前言
理解进程和线程的概念,对于网络运维或者编写程序都是十分重要的。无论是windowx平台或者linux平台,甚至包括手机,如果你想要运行任何一个程序,都和进程、线程脱离不了干系。
概念
我们可以把一个程序理解为一个进程,这里我们拿谷歌浏览器举例程序举例,当你运行谷哥浏览器程序后,在你的任务管理器里面就会出现该程序的进程。
image.png
从上图可以很清楚的看到系统为谷歌浏览器(Google Chrome)创建了1个进程,并且该进程创建了34个线程,这里就出现了一个进程与线程之间关系的概念。一个进程可以拥有多个线程,就像你只有2只手,但你有10个手指头。
进程是大爷
进程实际上是不做事的,而是吩咐别人做事,这个“别人”当然只有线程这位苦力了,当一个程序(进程)想要做一件事,比如刷新网页,他就会开启一个线程,让这个线程去做这件事。
多线程
前面提过,一个进程可以有多个线程。有一点要注意,一个进程至少要有一个线程。这也很好理解,没有线程就没有人“干活”了。
还是以浏览器举例,假设我想一边听歌一边写博客,那么我必须打开2个网页,一个用来听歌,一个用来写作。这时候浏览器有2种方案处理,一种是只开一个线程,先执行听歌的任务,等歌播放任务结束后,再让线程执行写作任务。这就是单线程串行执行。
不过如果这种方式执行,我估计写博客的想砸电脑。另一种就是多线程方案。浏览器开启2个线程,1个线程负责播放音乐,另1个线程负责打开博客供用户写作。相当于并行处理两个任务,但这里的并行有时候并不是真正的并行,而是将CPU的的时间进行分片,以1秒时间为例,CPU将这1秒时间进行分片,每一片的单位是0.001秒,两个线程轮番占用CPU的时间,最终在2个线程优先级相同的情况下,会各占用CPU 0.5秒的时间。但因为这个切换太快了,用户是根本感觉不到的。但这种机制并不是真正的并行。
线程优先级
线程是有优先级的,优先级越高,它能获取的CPU处理时间越多,反之亦然。我们用程序在测试线程的优先级.
#以Objective C为程序示例
#创建了2个线程,一个要执行doMusic任务,一个执行doBlog任务
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(doMusic) object:nil];
NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(doBlog) object:nil];
#设备线程的名称,方便区别
thread1.name = @"播放音乐";
thread2.name = @"博客写作";
#重点,设置线程的优先级,1为最高
thread1.threadPriority = 1;
thread2.threadPriority = 0.5;
#让线程开始执行
[thread1 start];
[thread2 start];
#播放音乐执行的任务
- (void) doMusic {
for (int i=0; i < 10; i++) {
#输入当前线程的名称和循环数i的值
NSLog(@"%@---%zd",[NSThread currentThread].name,i);
}
}
#博客写作执行的任务
- (void) doBlog {
for (int i=0; i < 10; i++) {
NSLog(@"%@---%zd",[NSThread currentThread].name,i);
}
}
以下是程序运行结果
image.png
我们可以观察到2件事,第一,两条线程的确是交叉执行的,和我们前面说的时间分片一致;第二,优先级较高的“播放音乐”线程很快就执行完了10次循环,也就是说在同一个时间段内,“播放音乐”线程得到了更多的CPU时间资源,这就是优先级的作用。
线程的生命周期
此节,我们来观察一个线程从生到死的过程,是不是觉得有点残忍?
新建状态
每个线程被创建时都会经历的状态,这个时候的线程是不会被CPU调度的;
可运行状态
当线程被允许执行(由程序操控)时,就会进入可运行状态(runnable),此时该线程会被放入CPU资源调度池(Pool)中,由CPU根据线程的优行级决定何时执行。
运行状态
线程被CPU调度时,就处于运行状态,此时线程开始占用CPU处理时间。
阻塞状态
线程因CPU调度或者程序内部控制等原因(比如等待用户输入)将运行状态改变为阻塞状态(block),此状态下,线程不再占用CPU时间,但线程并没有被释放,而是等待某种条件达成后可再次转换为可运行状态,注意阴塞状态是不能直接转换为运行状态的。
释放状态
就是线程的所有工作都执行完毕,就像上面举例中的10次循环结束,此时线程迎来了它的终点,这也是每一个线程的终点,在他们干完自己的工作后,就会被释放掉(dead),这是为了回收该线程所占用的系统资源。
多线程安全问题
如果两个线程访问同一个资源的时候,可能会出现“安全”问题。
image.png我们还是看图说话,图有有2个线程,A线程和B线程,他们是并发执行的关系,有一个整型变量,它的初始值是17。首先A线程进行了读取变量的操作,读取的值是17,B线程在之后也读取了该变量,读取值为17.A线程将该值+1,然后写回给变量,此时变量的值变为18.那么问题来了,此时B线程在之前也是读取的17,B线程也给变量加+1,然后再写回,这样本来变量被加了2次,正确值应该是19,但最后结果是18.
你可能觉得这并不严重?
把17想像成你的银行存款,假设你有17万元存款,你的家人A往你帐户存了1万,同一时间你的家人B在另一个地方往你帐户也了一万,如果他们是按这种先后时间进行的,恭喜你,你的银行存款就只有18万,而不是19万,知道问题的严重性了吧?
解决方法
解决的方法就是“加锁”,当A线程准备读取数据时,就对要读取的变量加锁,让其它线程暂无法访问。待A进程操作完毕写入数据后再对该变量进行解锁操作,接着B线程再对数据进行读写操作。这样数据我们就认为“安全”了。但代价是B线程有2段时间处于“闲置”状态。
最后
理解了进程和线程的概念,我们才能够在日常的运维工作中深入的追究问题;也只有掌握了他们的概念,我们在编写代码时,写出效率与性能兼顾的好程序。你已经掌握他们的概念了吗?
网友评论