线程入门

来源:互联网 发布:方舟低配优化补丁 编辑:程序博客网 时间:2024/06/04 01:18

线程的相关知识总是让人容易犯迷糊,虽然初级的应用很简单,但是当面对复杂的线程使用时如果使用不知其所以然的知识,那么我想,得到解决方案也应该需要很长时间,那么不妨驻足,停下来理一理脉络。

不敢谈些深奥的东西,简单梳理一下基础,我计划从概念、作用、使用来介绍线程相关的有些知识。可能更着重说明一下使用。

本文主要参考JAVA中线程到底起到什么作用![1],内容真的很棒,但是阅读排版可能不大友好,所以加上我自己的理解再次进行了整理。

概念

什么是线程?要搞清楚这个概念也许很简单,百度一下。可学术回答太过专业。如果对于名词不大熟悉,有答案也看不明白。所以我采用带有偏差的语句来解释,如果觉得有必要深入一步,请继续查阅相关资料。

进程和线程往往一起出现,在此我不打算多谈进程。

只是简单的来一句:

一个具有一定独立功能的程序关于某个数据集合的一次运行活动,是系统进行资源分配和调度运行的基本单位[2]

更简单的:

进程就是在某种程度上相互隔离的、独立运行的程序[3]

线程呢?

线程也称作轻量级进程。就象进程一样,线程在程序中是独立的、并发的执行路径,每个线程有它自己的堆栈、自己的程序计数器和自己的局部变量。但是,与分隔的进程相比,进程中的线程之间的隔离程度要小。它们共享内存、文件句柄和其它每个进程应有的状态[3]

那么,二者的关系如何?

进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

打个比方:

对于一个国家,每个城市既有联系又维持独立,对于国家而言,一个大的区域就被拆成了小的区域,便于管理且高效。一个城市如果崩溃或被占,如果不作处理,其他城市仍能正常运转,国家不至于毁灭。但对于一个城市,不管是人口、经济、军事、政治、公共资源任何运作出现崩溃或瓦解,如果不作处理,那么城市肯定无法正常运转。国家就是系统,各个城市就是各个进程,城市的方方面面就是线程。

即,系统由多个进程组成,如果不作处理,单个进程的崩溃不会对系统造成极大影响。进程由多个线程组成,如果不作处理,单个线程的崩溃会对进程造成极大影响。

PS:请勿延伸此类比,理解就行了。

作用

线程这么脆弱,有什么用呢?一个没用,多了就有用。谈线程作用必谈多线程。

这么解释问题吧[4]

1.单进程单线程:一个人在一个桌子上吃菜。

2.单进程多线程:多个人在同一个桌子上一起吃菜。

3.多进程单线程:多个人每个人在自己的桌子上吃菜。

 

我在此仍然不会对多进程进行展开,只要这么理解就行了:进程的创建比线程开销更大。这是显而易见的,而且,多线程之间的资源交换远比多进程来得方便。

食客都能一直观察到自己这桌饭菜的情况,但无法时刻去看着其他桌子。

可以看到,多线程可以保证系统资源不被浪费(桌上的菜,一个食客不吃,还有其他食客吃),且相比多进程开销大(找人吃饭都乐意,找人造桌子就麻烦)且资源共享不便(要想共享吃饭的时候就得关注其他桌子)。

这就是多线程的作用:相比多进程,以一种更廉价方便的方式来处理任务或资源。

使用

同样的,对于线程的使用其实没什么用,关键是多线程的使用。

当多个线程同时读写同一份共享资源的时候,可能会引起冲突[1]

打个比方,多个人同时吃一道菜的时候容易发生争抢,例如两个人同时夹一个菜,一个人刚伸出筷子,结果伸到的时候已经被夹走菜了…此时就必须等一个人夹一口之后,在还给另外一个人夹菜,也就是说源共享就会发生冲突争抢。

多线程的使用最关键的就是解决竞用共享资源的问题。至于跟踪优化和控制就是进阶了。

这时候,我们需要引入线程“同步”机制,即各位线程之间要有个先来后到,不能一窝蜂挤上去抢作一团。

同步[1]
概念

这个词是从英文synchronize(使同时发生)翻译过来的。我也不明白为什么要用这个很容易引起误解的词。既然大家都这么用,咱们也就只好这么将就。

线程同步的真实意思和字面意思恰好相反。线程同步的真实意思,其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。

因此,关于线程同步,需要牢牢记住的是:

1.线程同步就是线程排队。同步就是排队。线程同步的目的就是避免线程“同步”执行。这可真是个无聊的绕口令。

2.“共享”这两个字。只有共享资源的读写访问才需要同步。如果不是共享资源,那么就根本没有同步的必要。

3.只有“变量”才需要同步访问。如果共享的资源是固定不变的,那么就相当于“常量”,线程同时读取常量也不需要同步。至少一个线程修改共享资源,这样的情况下,线程之间就需要同步。

4.多个线程访问共享资源的代码有可能是同一份代码,也有可能是不同的代码;无论是否执行同一份代码,只要这些线程的代码访问同一份可变的共享资源,这些线程之间就需要同步。

使用

同步锁模型

线程同步的基本实现思路还是比较容易理解的。我们可以给共享资源加一把锁,这把锁只有一把钥匙。哪个线程获取了这把钥匙,才有权利访问该共享资源。

但是现代的编程语言的设计思路都是把同步锁加在代码段上。确切的说,是把同步锁加在“访问共享资源的代码段”上。这一点一定要记住,同步锁是加在代码段上的。具体原因请阅读[1]。

首先,我们已经解决了同步锁加在哪里的问题。我们已经确定,同步锁不是加在共享资源上,而是加在访问共享资源的代码段上。

其次,我们要解决的问题是,我们应该在代码段上加什么样的锁。这个问题是重点中的重点。这是我们尤其要注意的问题:访问同一份共享资源的不同代码段,应该加上同一个同步锁;如果加的是不同的同步锁,那么根本就起不到同步的作用,没有任何意义。这就是说,同步锁本身也一定是多个线程之间的共享对象

成功获取同步锁的线程,执行完同步代码段之后,会释放同步锁。该同步锁的就绪队列中的其他线程就继续下一轮同步锁的竞争。成功者就可以继续运行,失败者还是要乖乖地待在就绪队列中。 因此,线程同步是非常耗费资源的一种操作。我们要尽量控制线程同步的代码段范围。同步的代码段范围越小越好。我们用一个名词“同步粒度”来表示同步代码段的范围。

信号量模型

同步锁模型只是最简单的同步模型。同一时刻,只有一个线程能够运行同步代码。

有的时候,我们希望处理更加复杂的同步模型,比如生产者/消费者模型、读写同步模型等。这种情况下,同步锁模型就不够用了。我们需要一个新的模型。这就是我们要讲述的信号量模型。

信号量模型的工作方式如下:线程在运行的过程中,可以主动停下来,等待某个信号量的通知;这时候,该线程就进入到该信号量的待召(Waiting)队列当中;等到通知之后,再继续运行。

很多语言里面,同步锁都由专门的对象表示,对象名通常叫Monitor。

信号量模型要比同步锁模型复杂许多。一些系统中,信号量甚至可以跨进程进行同步。另外一些信号量甚至还有计数功能,能够控制同时运行的线程数。

我们没有必要考虑那么复杂的模型。所有那些复杂的模型,都是最基本的模型衍生出来的。只要掌握了最基本的信号量模型——“等待/通知”模型,复杂模型也就迎刃而解了。

 

总结:

线程是什么,我认为线程的理解的重要之处在于其上下文,就是说它关联的那些些知识。

现在我也许能回答一下线程了:

单个无用,但它设计的初衷就是一大家子一同工作,但如何协调好这一家子才是关键。

 

[1]  http://www.cnblogs.com/ribavnu/archive/2012/11/05/2756183.html    JAVA中线程到底起到什么作用!

[2]  https://baike.baidu.com/item/%E8%BF%9B%E7%A8%8B/382503     进程

[3]  https://www.ibm.com/developerworks/cn/education/java/j-threads/j-threads.html     线程基础

[4]  https://www.zhihu.com/question/19901763    多线程有什么用?

原创粉丝点击