1.进程和线程以及它们的区别
1.1 进程是具有一定功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源调度和分配的一个独立单位。
1.2 线程是进程的实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。
1.3 一个进程可以有多个线程,多个线程也可以并发执行
简单描述:
1.资源调度分配的独立单位
2. CPU调度分派的基本单位
一个进程可以有多个线程(至少一个),多个线程也可以并发执行。
进程作为资源(如内存)分配的基本单位,作为其下属的线程都是可以享用其被分配到的资源的,而且线程可以共享同一块被分配的资源。而进程之间是一般不能分享彼此的资源的,进程想要互相通信,必须通过进程间通信(Inter-process communication,IPC)的机制来完成,主要包括以下几种:
(1)管道(pipe,半双工),流管道(s_pipe,全双工),有名管道(FIFO,全双工)
(2)信号量(sophomore/mutex)
(3)信号(signal)
(4)消息队列
(5)共享内容
(6)套接字(socket)
线程可以再分为两类:
一类是用户级线程(user level thread)。对于这类线程,有关线程管理的所有工作都由应用程序完成,内核意识不到线程的存在。在应用程序启动后,操作系统分配给该程序一个进程号,以及其对应的内存空间等资源。应用程序通常先在一个线程中运行,该线程被成为主线“程。在其运行的某个时刻,可以通过调用线程库中的函数创建一个在相同进程中运行的新线程。 用户级线程的好处是非常高效,不需要进入内核空间,但并发效率不高。
另一类是内核级线程(kernel level thread)。对于这类线程,有关线程管理的所有工作由内核完成,应用程序没有进行线程管理的代码,只能调用内核线程的接口。内核维护进程及其内部的每个线程,调度也由内核基于线程架构完成。内核级线程的好处是,内核可以将不同线程更好地分配到不同的CPU,以实现真正的并行计算。
事实上,在现代操作系统中,往往使用组合方式实现多线程,即线程创建完全在用户空间中完成,并且一个应用程序中的多个用户级线程被映射到一些内核级线程上,相当于是一种折中方案。
进程和线程的区别包括:
-
一个程序至少有一个进程,一个进程至少有一个线程
-
线程的划分尺度小于进程,使得多线程程序的并发性高
-
进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率
-
每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制
-
多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配
2.进程间的通信
主要分为:管道、系统IPC(包括消息队列、信号量、共享存储)、SOCKET
管道主要分为:普通管道PIPE 、流管道(s_pipe)、命名管道(name_pipe)
1、管道是一种半双工的通信方式,数据只能单项流动,并且只能在具有亲缘关系的进程间流动,进程的亲缘关系通常是父子进程
2、命名管道也是半双工的通信方式,它允许无亲缘关系的进程间进行通信
3、信号量是一个计数器,用来控制多个进程对资源的访问,它通常作为一种锁机制。
4、消息队列是消息的链表,存放在内核中并由消息队列标识符标识。
5、信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
6、共享内存就是映射一段能被其它进程访问的内存,这段共享内存由一个进程创建,但是多个进程可以访问
3.线程同步
1、互斥量:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。
2、信号量:它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量。
3、事件(信号):通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作。
解析:
首先要明白,什么是线程同步,为什么要同步?
所谓同步,就是并发的线程在一些关键点上可能需要互相等待与互通信息,这种相互制约的等待与互通信息称为进程同步。
“同”其实是协同,而不是同时,因为我们知道多线程终究是不能同时执行的(看起来那么多程序同时运行互相不干扰那是从宏观层面看是这样),那么线程之间由于执行权在不断地切换,如果不同线程都做不同的事,处理不同的数据倒也没什么问题,关键是有时候会有那么一些数据,是被多个线程共享的,大家都有可能对这些数据进行一些操作。
比如,银行系统同时收到两个请求向同一个账户A存钱,都是要存入100元.账户A中本来有1000元。那么系统对两个请求肯定是分别生成一个线程去完成各自的对账户进行加上100元的操作,可是当其中一个线程1运行完以下语句:
account=account+100
当线程1读取到account在内存中的值1000并加上了100得到1100,但还没更新到数据库中时,线程1由于丧失了执行权,线程2来接手了,线程2也执行这一句,但它读到的account值依然是1000(数据库的数据尚未更新),也把它加上100,变成1100,然后要更新到数据库。那后面不管谁先更新到数据库,数据库里A账户的记录都只会是1100,而不是1200了,这就出岔子了。
要防范这种问题也不难,我们可以让给执行账户值更改一直到把新的值更新到数据库里的整个操作给”封闭“起来,一次只允许一个线程来对同一个账户执行这样一些操作,如果多个线程想同时来执行这个操作,对不起,因为这些操作被”封闭“了,不等里面的一个线程把它要做的修改更新到数据库之前,其它线程一概不许进入,乖乖在外面排队等着吧。
这只是一个简单的例子,于是我们需要一些机制来保证这样的问题不会出现,让不同的线程在操作这样一些共同的数据时不会出问题,再或者是有时候有些事必须等A线程做完了,B线程才能接着后面的做,我们要让这些需要多个线程合作的任务都正常进行,因此就要让它们”同步“!
这里再引入一个临界资源与临界区的概念:
临界资源是指一次仅允许一个线程使用的资源,许多物理设备,如打印机都有这种性质。除了物理设备外,还有一些软件资源,若被多线程所共享也具有这一特点,如变量、数据、表格、队列等。它们虽可以为若干线程所共享,但一次只能为一个线程所利用。
临界区指的是一个访问共用资源(被多个线程共享的临界资源)的程序片段,而这些共用资源又无法同时被多个线程访问的特性。当有线程进入临界区段时,其他线程或是进程必须等待(例如:bounded waiting 等待法),有一些同步的机制必须在临界区段的进入点与离开点实现,以确保这些共用资源是被互斥获得使用,例如:semaphore。只能被单一线程访问的设备,例如:打印机。
如上所述,同步机制所要解决的绝大多数问题,都出在临界区这儿,我们后面的同步机制都是在临界区上做文章,以避免出现问题。
同步有以下这样些个方式/机制:
(1)互斥量(mutex):互斥量是一种公共资源,在指定时刻,它只能被一个线程占有(也就是所有权特性),而且占有它的线程可以反复申请这个互斥量。只有拥有互斥量的线程才有访问公共资源的权限。因为互斥量只有一个,所以可以保证公共资源不会被多个线程同时访问。(比如Java中的synchronized代码块,需要你提供一个类的对象或Class类作为锁,这个锁就可以理解为互斥量)
(2)信号量(semaphore):每个信号量都是公共资源,其值是一个32位计数。信号量的数据结构为一个值和一个指针,指针指向等待该信号量的下一个进程。信号量的值与相应资源的使用情况有关。当它的值大于0时,表示当前可用资源的数量;当它的值小于0时,其绝对值表示等待使用该资源的进程个数。注意,信号量的值仅能由PV操作来改变。
实现的P,V操作算法描述:
P操作:while s>0
s=s-1,
V操作:s=s+1。
P表示申请一个资源,如果条件满足(即右可以分配的资源),则把资源分配给提出申请的进程,并且时资源数目s减1。V表示资源使用哪完毕之后,要把占有的资源释放,并且资源数目s加1 。
(3)事件(信号 signal):通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作。(比如Java中的notify()唤醒wait()状态的阻塞线程)