本来以为自己对于这方面应该还算了解了,但是昨天被人一问竟然无法明确的解释出来,解释的很挫,今天详细的整理了一下思路,总结如下:
首先,我们用最简单的一句话表明:
进程:程序的一次执行;
线程:CPU调度的基本单位。
简言之:一个程序至少包含一个进程(例如,我们打开酷狗音乐的客户端,除了客户端进程外,我们还会在后台看到这个程序包含的一个守护进程(daemon进程)),一个进程至少包含一个线程。
这里我想先做一个类比,然后在详细整理一些具体的概念:
由于本人是在东北长大的,高中时代,到了冬天,最讨厌的就是一旦下雪,学校就会分配一个班到操场进行清雪的任务,这里,我把学校类比成操作系统,而每一个独立的班级比作成一个进程,班级的班主任比喻成主线程,而我们每个学生,就是苦逼的干活的子线程;
1、首先,当操场布满大雪后,学校发布通知,今天轮到我们30班(我的高中班号)进行操场的清雪任务。
---即操作系统启动运行程序,CPU被分配给了我们这个进程,假设是单核CPU,操作系统同一时刻只能运行一个进程;
2、由于学校不会指定某个班级的哪几个学生来完成清扫任务,学校只会分配这次的清扫任务应该由哪个班级执行。
---即操作系统并不会将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源的分配,进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程才是操作系统进行资源分配和调度的一个独立单位;
3、然后,班主任作为领导,开始为我们几个值日的学生分配清扫任务,由谁来清扫东北角,由谁来清扫西南角等等,
---即当进程启动后,主线程调用pthread_create创建了多个子线程,主线程不干活,只负责任务的调度,调度完成调用pthread_join阻塞自身,等待子线程完成;
4、当我们几个值日生苦逼的领到各自的任务之后,其实整个操场都是我们的战场,我们共享这一片操场,准备在这操场上挥洒汗水。
---即一个进程中所有的线程共享进程所拥有的的全部资源;
5、由于我们之前受过班主任的任务分配,我们要打扫自己所负责的部分,这时,我们高高兴兴的去班级卫生角拿清雪工具,结果悲剧的发现,我们8个人,却只有一个清雪的铲子,5个扫帚,这就尴尬了,怎么办?说时迟,那时快,我还没反应过来,一个同学过去就把雪铲给抢到手了,另外5个同学也毫不示弱,瞬间又把扫帚抢到手,就剩下我和另外一个傻蛋,愣在原地,当然,按道理来说,班级的资源我们是共享的,但是雪铲只有一把,只能让反应快的同学先用,等他铲一会铲累了,我再把雪铲拿过来自己用。
---这里的雪铲,就是一把锁,谁抢到谁用,在单核CPU的多线程中,CPU会不断的进行上下文切换来保障多线程的并发,(而并行只存在于多核CPU中,多个CPU可以在同一时刻并行的执行多个进程),为了保障多个线程同时读写某一个内存区域,引入锁的概念,也就是说,这把雪铲谁拿到,谁就去清扫自己负责的区域,其他线程将投入睡眠,等这个人铲累了,其他投入睡眠的线程醒来再去争抢这把锁。此外,五把扫帚也是扫雪的工具,我们可以把它看成“信号量”,(其实这个比喻不太恰当,因为“信号量”实际上是一种IPC的方式,是锁的属性之一:进程共享属性,用来协调不同进程间的数据对象的同步原语,它本质上可以看成是一个计数器),当五把扫帚都有人用时,其他的线程只能投入睡眠等待,当计数器加1时,便会向等待的线程发送信号,空出来的“扫帚”也没必要争抢了,剩我们两个就排个序先后用了,但是这种“扫帚”既费力,干活又慢,显然没有“雪铲”好用,所以,应该根据具体情况,来选用是采用信号量的方式还是互斥锁的方式;
6、就这样,我们几个值日的同学通过争抢那个唯一的雪铲,最终陆陆续续的铲完自己负责的区域,回到班级,班主任看到我都都回到了班级,完成了整个操场的清雪任务,满意的笑了笑,向学校报告,圆满完成了今日的30班清雪任务。
---主线程一直阻塞或者并发的执行其他工作,当子线程完成工作以后,对子线程的资源进行回收,然后向操作系统返回执行的结果。
以上类比的思路来自阮一峰老师的博客:http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html,并且进行了相关的扩充。
好了,类比工作完成了,下面我们来总结一下里面涉及到的概念:
进程:是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是OS进行资源分配和调度的一个独立单位;
线程:是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。(即,每个学生所拥有的工具都是班级的公共财产,而不能说是学校的公共财产)。一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行。
区别总结:进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉(针对于单线程的情况,或者说主线程死掉),所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
此外,在类比中,还提到了锁的概念,下面也简单记录下APUE中的阐述:
当多个线程试图在同一时间修改同一变量时,需要进行同步,我们以增加操作为例,分为3步:
(1)、从内存单元读入寄存器;
(2)、在寄存器中对变量做增量操作;
(3)、把新的值写回内存单元。
注意:如果该操作是原子操作,那么就不存在竞争(因为中间不存在context switch)。
线程同步原语的五种方式:
(1)、互斥量
互斥量其实是一把锁,多线程在访问共享资源前对互斥量加锁,在访问结束后释放互斥锁。它只有加锁和不加锁两种状态。
(2)、读写锁
读写锁相对于互斥锁来说,多了一种状态,即在加锁的状态中,分为了读锁和写锁两种,一次只有一个线程可以占有写模式的锁,但是多个线程可以同时占有读模式的锁。(这在数据库中应用)
(3)、条件变量
条件变量给多个线程提供了一个会和的场所,条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。条件本身是由互斥量保护的,线程在改变状态之前,必须先锁住互斥量,其他线程在获得互斥量之前不会察觉到这种状态,因为互斥量必须在锁定以后才能计算条件。
(4)、自旋锁
自旋锁不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等阻塞状态,在自旋时,会占用CPU的时间,不断“询问”锁是否可用,这样,CPU就不能进行上下文切换去做其他的事情。所以自旋锁应该应用于锁被持有的时间短的情况,线程并不希望在重新调度上花费太多的成本。
(5)、屏障
屏障是用户协调多个线程并行工作的同步机制,(类似于我们在python中的threading模块的join方法,父线程阻塞等待所有的子线程完成工作在退出,也就是类比中的我们的“班主任”),pthread_join函数就是一种屏障,运行一个线程等待,直到另一个线程退出。
这里只简单介绍下,之后有时间再详细整理同步原语以及IPC方面的知识。
参考博客:http://www.cnblogs.com/lmule/archive/2010/08/18/1802774.html