美文网首页
通过实例理解进程与线程的概念

通过实例理解进程与线程的概念

作者: 相当嚣张 | 来源:发表于2018-10-01 15:10 被阅读63次

    前言

    理解进程和线程的概念,对于网络运维或者编写程序都是十分重要的。无论是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万,知道问题的严重性了吧?

    解决方法

    image.png
    解决的方法就是“加锁”,当A线程准备读取数据时,就对要读取的变量加锁,让其它线程暂无法访问。待A进程操作完毕写入数据后再对该变量进行解锁操作,接着B线程再对数据进行读写操作。这样数据我们就认为“安全”了。但代价是B线程有2段时间处于“闲置”状态。

    最后

    理解了进程和线程的概念,我们才能够在日常的运维工作中深入的追究问题;也只有掌握了他们的概念,我们在编写代码时,写出效率与性能兼顾的好程序。你已经掌握他们的概念了吗?

    相关文章

      网友评论

          本文标题:通过实例理解进程与线程的概念

          本文链接:https://www.haomeiwen.com/subject/kucnoftx.html