栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

并发编程概述(进程、IO多路复用、线程)

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

并发编程概述(进程、IO多路复用、线程)

现代操作系统提供了三种基本的构造并发程序的方法 :
1.进程:用这种方法,每个逻辑控制流都是一个进程,由内核来调度和维护。因为进程有独立的虚拟地址空间,想要和其他流通信,控制流必须使用某种显式的进程间通信 (interprocess communication, IPC) 机制。
2.I/O多路复用:在这种形式的并发编程中,应用程序在一个进程的上下文中显式地调度它们自己的逻辑流。逻辑流被模型化为状态机,数据到达文件描述符后,主程序显式地从一个状态转换到另一个状态 。因为程序是一个单独的进程,所以所有的流都共享同一个地址空间。
3.线程:线程是运行在一个单一进程上下文中的逻辑流,由内核进行调度 。你可以把线程看成是其 他两种方式的混合体,像进程流一样由内核进行调度,而像I/O多路复用流一样共享同一个虚拟地址空间。

一、基于进程的并发编程

        进程的优劣:对于在父子进程间共享状态信息,进程有一个非常清晰的模型:共享文件表,但是不共享用户地址空间。进程有独立的地址空间既是优点也是缺点。这样一来,一个进程不可能不小心覆盖另一个进程的虚拟内存,这就消除了许多令人迷惑的错误,这是一个明显的优点。
        另一方面 ,独立的地址空间使得进程共享状态信息变得更加困难 。为了共享信息,它们必须使用显式的 IPC (进程间通信)机制 。基于进程的设计的另一个缺点是,它们往往比较慢,因为进程控制和 IPC 的开销很高。

注:进程间通信机制包括信号、socket、管道、共享内存、信号量

二、基于I/O多路复用的并发编程

        I/O多路复用优缺点:事件驱动设计的一个优点是,它比基于进程的设计给了程序员更多的对程序行为的控制 。例如,我们可以设想编写一个事件驱动的并发服务器,为某些客户端提供它们需要的服务,而这对于基于进程的并发服务器来说是很困难的。
        另一个优点是,一个基于 I/O 多路复用的事件驱动服务器是运行在单一进程上下文中的,因此每个逻辑流都能访问该进程的全部地址空间。这使得在流之间共享数据变得很容易。一个与作为单个进程运行相关的优点是,你可以利用熟悉的调试工具,例如 GDB来调试你的并发服务器,就像对顺序程序那样 。最后,事件驱动设计常常比基于进程的设计要高效得多,因为它们不需要进程上下文切换来调度新的流。
        事件驱动设计一个明显的缺点就是编码复杂。我们的事件驱动的程序所需要的代码比基于进程的相同功能的程序的代码多三倍,并且很不幸,随着并发粒度的减小,复杂性还会上升 。这里的粒度是指每个逻辑流每个时间片执行的指令数量 。只要某个逻辑流正在运行,其他逻辑流就不可能有进展。基于事件的设计另一个重要的缺点是它们不能充分利用多核处理器。

三、基于线程的并发编程

        基于线程是以上两种方法的混合。线程(thread) 就是运行在进程上下文中的逻辑流 。现代系统 允许我们编写一个进程里同时运行多个线程的程序 。线程由内核自动调度。每个线程都有它自己的线程上下文 (thread context),包括一个唯一的整数线程 (Thread ID,TID)、栈、栈指针、程序计数器 、通用目的寄存器和条件码 。所有的运行在一个进程里的线程共享该进程的整个虚拟地址空间。
        基于线程的逻辑流结合了基于进程和基于 I/O 多路复用的流的特性 。同进程一样,线程由内核自动调度,并且内核通过一个整数 ID 来识别线程。同基于 I/O 多路复用的流一样,多个线程运行在单一进程的上下文中,因此共享这个进程虚拟地址空间的所有内容,包括它的代码 、数据 、堆 、共享库和打开的文件。        

        在任何一个时间点上,线程是可结合的 (joinable) 或者是分离的(detached)。 一个可结合的线程能够被其他线程收回和杀死 。在被其他线程回收之前,它的内存资源(例如栈)是不释放的 。相反 ,一个分离的线程是不能被其他线程回收或杀死的。它的内存资源在它终止时由系统自动释放。默认情况下,线程被创建成可结合的 。为了避免内存泄漏,每个可结合线程都应该要么被其他线程显式地收回,要么通过调用pthread detach 函数被分离。(join:等待某一线程结束,回收其资源;detach:不需要等待某线程,分离此线程,在此线程终止时会自动释放资源)

        从我们程序员的角度来看,线程很有吸引力的一个方面是多个线程很容易共享相同的程序变量 。然而,这种共享也是很棘手的 。为了编写正确的多线程程序,我们必须对所谓的共享以及它是如何工作的有很清楚的了解。为了理解 C 程序中的一个变量是否是共享的,有一些基本的问题要解答:(1)线程的基础内存模型是什么?(2)根据这个模型,变量实例是如何映射到内存的?(3)最后,有多少线程引用这些实例?一个变量是共享的,当且仅当多个线程引用这个变量的某个实例。
1.线程内存模型
        一组并发线程运行在一个进程的上下文中。每个线程都有它自己独立的线程上下文,包括线程 ID、栈、栈指针 、程序计数器 、条件码和通用目的寄存器值 。每个线程和其他线程一起共享进程上下文的剩余部分。这包括整个用户虚拟地址空间,它是由只读文本(代码 )、 读 / 写数据 、堆以及所有的共享库代码和数据区域组成的。线程也共享相同的打开文件的集合。
        从实际操作的角度来说,让一个线程去读或写另一个线程的寄存器值是不可能的 。另一方面 ,任何线程都可以访问共享虚拟内存的任意位置 。如果某个线程修改了一个内存位置,那么其他每个线程最终都能在它读这个位置时发现这个变化。因此,寄存器是从不共享的,而虚拟内存总是共 享的。

2.将变量映射到内存
        多线程的C程序中变量根据它们的存储类型被映射到虚拟内存:
        (1)全局变量。 全局变量是定义在函数之外的变量 。在运行时,虚拟内存的读 /写区域只包含每个全局变量的一个实例,任何线程都可以引用。
        (2)本地自动变量 。本地自动变量就是定义在函数内部但是没有 static 属性的变量。在运行时,每个线程的栈都包含它自己的所有本地自动变量的实例。即使多个线程执行同一个线程例程时也是如此 。
        (3)本地静态变量 。本地静态变量是定义在函数内部并有 static 属性的变量。 和全局变量一样,虚拟内存的读 / 写区域只包含在程序中声明的每个本地静态变量的一个实例 。

3.共享变量

        我们说一个变量是共享的,当且仅当它的一个实例被一个以上的线程引用。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/748646.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号