进程与线程

1.进程

1.1概念

1)为实现操做系统的并发性和共享性,引入进程html

2)进程是程序执行时的一个实例,相似于一个活动,它有程序、输入、输出和状态这几个部分组成。举个例子,一个程序员为他的女儿作蛋糕,他有作蛋糕的食谱和各类原料。食谱是程序用某种形式描述的算法),程序员是CPU,各类原料是输入数据。进程就是程序员阅读食谱、取来各类原料来制做蛋糕一系列动做的总和。同时,进程它也有状态,假设这个程序员在作蛋糕的时候,他的儿子哭着跑过来,说他的手被小刀割破了,程序员此时就记录下他按照食谱作到哪了(保存进程的当前状态,进程被挂起),而后按照急救手册处理伤口。这里,就至关于CPU从一个进程切换到另外一个高优先级的进程。当伤口处理完后,程序员又回去作蛋糕,从他所记录的地方继续作下去。(程序和进程的区别能够用这个作蛋糕的例子来讲明)linux

3)在进程模型中,进程是资源分配和独立调度的基本单位程序员

主要是记前3点算法

4)进程具备五大特性缓存

  动态性(最基本):进程具备生命周期,是动态地建立、变化、结束的。安全

  并发性:多个进程能够并发运行。数据结构

  独立性:进程是资源分配和独立调度的基本单位。多线程

  异步性:因为进程间共享资源和协同合做,所以产生相互制约的关系,使进程具备执行的间断性(走走停停)。并发

  结构性:每一个进程都配置一个PCB(进程控制块,一种数据结构)对其进行描述。异步

1.2进程映像

  某一时刻进程的内容和状态的集合,进程映像一般由程序块、数据块和一个PCB(进程控制块)组成,进程映像是静态的,进程是动态的。

1.3进程的伪并行

  严格说,CPU在某一瞬间只能运行一个进程。但在1秒钟内,它可能运行多个进程,这样就产生了并行的错觉。

1.4进程的状态模型

1.4.1三个状态

1)运行态:进程正在占用CPU的时期

2)就绪态:是一种运行的状态,由于其余进程正在运行而暂时中止

3)阻塞态:等待外部事件的发生(典型的例子:它在等待可以使用的输入)

1.4.2四种转换(见图)

1)运行态->阻塞态

2)运行态->就绪态

3)就绪态->运行态

4)阻塞态->就绪态:外部事件的一旦发生,阻塞态转换到就绪态,若是此时CPU空闲,马上发生转换3,转换到运行态

1.5进程的建立

新进程都是由已有进程而建立:

  UNIX:父进程使用fork建立子进程,内核会分配一个新的PCB给子进程,子进程是父进程的副本,子进程拷贝父进程的数据段、堆、栈,而正文段与父进程共享;可是fork以后经常跟随exec调用让子进程执行不一样于父进程的任务,因此fork会使用“写时复制”技术,开始的时候子父进程共享数据段、堆、栈和正文段,当任何一方想要更改某个区域时,内核再为相应区域制做副本

  WINDOWS:父进程使用CreateProcess建立子进程,子父进程从一开始就不共享任何区域。

1.6进程的实现

  操做系统维护一张进程表,每个进程占用一个表项,表项包含这个进程状态的各类信息。

1.7进程的终止

1.7.1正常终止

1)从main返回

2)在任何地方调用exit、_exit、_Exit(包括在进程的线程里调用)

3)进程中的最后一个线程从其启动例程返回

4)最后一个线程调用pthread_exit函数

1.7.2异常终止

1)调用abort函数(此函数将发送SIGABRT信号给进程)

2)接收到一个信号

3)最后一个线程取消请求作出响应

2.线程

2.1概念

1)线程是一种轻量级进程,更容易建立,也更容易销毁

2)在线程模型中,“资源分配”与“调度”分离,线程是独立调度的基本单位进程是资源分配的基本单位

3)线程的优点(为何要提出线程):提高了操做系统的并发性能,首先线程是轻量级进程,更容易建立,也更容易销毁,其次,进程切换须要切换虚拟地址空间,而线程切换则不须要,减小了切换的性能损耗

2.2进程与线程的区别与联系

1)一个进程能够有多个线程,但至少有一个线程。

2)进程有独立的地址空间,同一进程下的多线程共享地址空间,更确切的说是共享正文段、数据段和堆,不共享栈,各线程拥有本身的栈

3)线程是调度的基本单位,进程是资源分配的基本单位,同一进程下的多线程共享该进程资源。

4)通讯方面:进程间通讯须要管道、消息队列、共享内存等手段,而同一进程下的多线程直接经过共享的数据段和堆来通讯

2.3进程与线程的选择取决如下几点

1)须要频繁建立销毁的优先使用线程,由于对进程来讲建立和销毁一个进程代价是很大的

2)线程的切换速度快,因此在须要大量计算,切换频繁时用线程,还有耗时的操做使用线程可提升应用程序的响应

3)由于对CPU系统的效率使用上线程更占优,因此可能要发展到多机分布的用进程,多核分布用线程;

4)须要更稳定安全时,适合选择进程

2.4线程的状态模型

  与进程同样,有三个状态,四种转换

2.5线程的建立

  进程的主线程使用thread_create建立线程,全部线程都是平等的

int pthread_create(pthread_t *restrict tidp, const pthread_attr_t * attr, void *(*func)(void*), void* arg );
//若成功则返回0,不然返回错误编号

2.6线程的分类

  1.用户级线程:不须要内核支持而在用户程序中实现的线程,内核对线程包一无所知,内核中用进程表管理进程,进程中用线程表管理多线程

  优势:1.能够在不支持线程的操做系统中实现  2.每一个进程可制订本身的调度算法调度线程

  缺点:1.发送系统阻塞时内核不知道多线程的存在于是阻塞进程从而阻塞全部线程  2.进程内部没有时钟中断,因此不能轮转调度线程

 

  2.内核级线程:由内核建立和撤销,内核用线程表来管理内核线程

  优势:1.一个线程阻塞,内核能够运行同一进程内的另外一个线程

  缺点:1.代价大:阻塞线程的调用都是系统调用、内核中建立和撤销的开销更大  2.信号是发给进程而不是线程,信号到达进程由哪个线程去处理?

2.7线程的终止

1)线程从启动例程中返回

2)线程被同一进程的其余线程取消

void pthread_cancel(pthread_t pid);
    //若成功,返回0;不然,返回错误编号

  某线程能够经过调用pthread_cancel函数来请求取消另外一线程,仅仅是请求,另外一线程能够忽略或者控制如何被取消

int pthread_setcancelstate(int state, int *oldstate);
//成功则返回0,不然返回错误编号。

  有两个线程并无包含在pthread_attr_t结构中,他们是可取消状态可取消类型,这两个属性影响着线程在响应pthread_cancel函数调用时所呈现的行为。可取消状态属性能够是PTHREAD_CANCEL_ENABLE和PTHREAD_CANCEL_DISABLE,线程的默承认取消状态是PTHREAD_CANCEL_ENABLE,可调用pthread_setcancelstate修改它的可取消状态,当状态设为disable时,对pthread_cancel的调用并不会杀死线程,而是挂起这个取消请求(若是以后取消状态再次变为enable,则挂起的请求能够被处理)

void pthread_testcancel(void);

  推迟取消:调用pthread_cancel之后,在线程到达取消点以前,并不会出现真正的取消。当调用下表中列出的任何函数,取消点都会出现,或者调用pthread_testcancel设置取消点

int pthread_setcanceltype(int type, int *oldtype);
//成功则返回0,不然返回错误编号。

  经过调用pthread_setcanceltype来修改取消类型,取消类型能够是PTHERAD_CANCEL_DEFERRED(延迟取消)或PTHREAD_CANCEL_ASYNCHRONOUS(异步取消),使用异步取消时,线程能够在任什么时候间取消,而不是非要等到遇到取消点才能被取消

3)线程调用pthread_exit函数

2.8线程之间的资源

2.8.1共享:

1)正文段

2)数据段(data段、bss段)

3)堆

4)进程ID

5)每种信号的处理方式

6)文件描述符

2.8.2独享:

1)

2)线程ID

3)线程的优先级(线程须要被调度)

4)信号屏蔽字(每一个线程所感兴趣的信号不一样,因此线程的信号屏蔽字由线程本身管理)

5)寄存器的值(线程间是并发运行的,每一个线程有本身不一样的运行线索)

6)错误返回码errno(同一个进程中有多个线程在同时运行,若是某个线程设置了errno值,当该线程还没来得及处理这个错误,另一个线程也设置了errno值,这样的话前一个线程的错误就没有办法被处理了。 因此,不一样的线程应该拥有本身的错误返回码变量)

2.9线程结合与分离

1)一个线程要么是结合的,要么是分离的

2)一个结合的线程能够被其余线程回收资源和杀死

3)一个分离的线程不能被别的线程回收资源和杀死,等到这个线程终止后,系统自动释放资源

4)默认状况下,建立的线程是结合的(设置pthread_create函数的参数可建立分离的进程)

5)若是一个结合的线程终止后却没有被pthread_join,则它将成为僵死线程(相似于僵死进程,还有一部分资源没有被回收),因此建立线程者应该调⽤用pthread_join来等待线程运行结束,回收线程资源

6)调用pthread_join后,当等待线程没有终止时,父线程将处于阻塞状态;若是要避免阻塞,在子线程中调用pthread_detach(pthread_self())或者父线程中调用pthread_detach(thread_id),这会将子线程的状态设置为分离的,这样一来该线程终止后系统会自动释放资源,父线程就不用阻塞等待了

2.10谨慎在多线程中使用fork

1)子进程继承父进程的互斥量、读写锁、条件变量

2)linux中,若是父进程包含多线程,fork的时候只复制调用fork的线程到子进程,其余线程在子进程中不被复制

3)假设在fork以前,父进程的一个线程对某个锁进行的lock,而后另一个线程调用了fork建立子进程。此时在子进程中持有那个锁的线程却不被复制,然而子进程又会继承父进程的锁,从子进程的角度来看,这个锁被“永久”的上锁了,由于它的持有者“蒸发”了。若是子进程再对这个锁进行lock的话,就会发生死锁。

4)解决

  • 在fork出的子进程中马上调用exec可避免这个问题,调用exec,旧的地址空间被抛弃,锁的状态就可有可无
  • 使用pthread_atfork函数,该函数用于清除锁状态,可是该函数不能清理条件变量

3.进程切换和线程切换

1)进程切换分两步:

  1. 切换虚拟地址空间
  2. 切换内核栈和硬件上下文

2)对于linux来讲,线程和进程的最大区别就在于地址空间,对于线程切换,第1步是不须要作的,第2是进程和线程切换都要作的

3)切换的性能消耗:

  1. 进程的切换要切换虚拟地址空间,会扰乱处理器的缓存机制:处理器中全部已经缓存的内存地址都做废了,处理的页表缓冲的TLB会被所有刷新,这将致使内存的访问在一段时间内至关得低效;可是在线程的切换中,不会出现这个问题
  2. 切换内核栈和硬件上下文会将寄存器中的内容切换出,产生性能损耗

4.进程和线程的函数对比

1)pthread_create()相似于fork(),用来建立线程

2)pthread_exit()相似于exit(),用来终止线程

3)pthread_self()相似于getpid(),获取线程号

4)pthread_join()相似于waitpid(),用来处理终止的线程

5.协程

1)是一种比线程更加轻量级的存在。正如一个进程能够拥有多个线程同样,一个线程能够拥有多个协程;

2)协程不是被操做系统内核管理,而彻底是由程序所控制

3)协程的开销远远小于线程

4)协程能保留上一次调用时的状态,每次过程重入时,就至关于进入上一次调用的状态:协程拥有本身寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其余地方,在切换回来的时候,恢复先前保存的寄存器上下文和栈

5)每一个协程表示一个执行单元,有本身的本地数据,与其余协程共享全局数据和其余资源

6)协程极高的执行效率,和多线程相比,线程数量越多,协程的性能优点就越明显;

参考资料:

https://m.jb51.net/article/102004.htm

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息