并发编程

并发,顾名思义,就是逻辑控制流在时间上重叠。并发机制运用在两个方面,一个是操作系统内核用于运行多个应用程序;一个是应用程序用于响应异步事件等,称为应用级并发,具体应用如下:

1、Linux信号处理程序允许应用响应异步事件,例如用户键入Ctrl+C(终止进程)会发生以下事件:

(1)用户输入命令,在Shell下启动一个前台进程

(2)用户按下Ctrl-C,这个键盘输入产生一个硬件中断;

(3)如果CPU当前正在执行这个进程的代码,则该进程的用户空间代码暂停执行,CPU从用户态切换到内核态处理硬件中断;

(4)终端驱动程序将Ctrl-C解释成一个SIGINT信号,记在该进程的PCB中(也可以说发送了一个SIGINT信号给该进程);

(5)当某个时刻要从内核返回到该进程的用户空间代码继续执行之前,首先处理PCB中记录的信号,发现有一个SIGINT信号待处理,而这个信号的默认处理动作是终止进程,所以直接终止进程而不再返回它的用户空间代码执行。

2、访问慢速I/O设备

3、与人交互。现代视窗系统利用并发来提供多任务功能,每次用户请求某种操作时,一个独立的并发逻辑流被创建来执行这个操作。

4、通过推迟工作以降低延迟。比如,一个动态内存分配器通过推迟将多次free合并为一次以降低总的延时。

5、服务多个网络客户端。并发服务器为每个客户端创建单独的逻辑流,允许服务器为多个客户端服务,同时也避免了慢速客户端独占服务器。

6、在多核计算机上进行并行运算。

构造并发程序的方法

使用应用级并发的应用程序称为并发程序。现代操作系统提供了三种基本的构造并发程序的方法,分别是进程、I/O多路复用和线程。

基于进程的并发编程

每个逻辑控制流都是一个进程,由内核来调度和维护。对于父子进程间共享状态信息,进程间共享文件表,但是不共享用户地址空间。

以构造并发服务器为例,服务器在父进程中接受客户端的请求,然后创建一个新的子进程来为每个新的客户端提供服务。注意要及时关闭父进程的连接描述符副本,否则将永不会释放已连接描述符的文件表条目,由此引起的内存泄漏将消耗光可用的内存。

进程的优劣:

(1)优点:由于每个进程有独立的虚拟地址空间,所以进程间不会发生虚拟内存覆盖问题。

(2)缺点:

  • ​ 为了共享信息,进程间必须使用显式的IPC(进程间通信)机制。
  • ​ 进程控制和IPC的存在使得基于进程构造的并发程序比较慢。

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

使用select函数,要求内核挂起进程,只有在一个或多个I/O事件发生后,才将控制返回给应用程序。

int select(int n, fd_set *fdset, NULL, NULL, NULL);
//fdset:读集合,n:该读集合的基数
//返回已准备好的描述符的非0的个数

select()处理类型为fd_set的集合,也叫做描述符集合,描述符集合被看成一个大小为n的位向量,每个位对应于一个描述符,当该位为1时表明该描述符是描述符集合的一个元素。只允许对描述符集合做3件事情:(1)分配它们,(2)赋值,(3)用FD_ZERO(所有位清零)、FD_SET(描述符fd对应的位置1)、FD_CLR(描述符fd对应的位置0)和FD_ISSET(判断描述符fd对应的位是否为1)宏来修改和检查它们。

select函数会一直阻塞,直到读集合中至少有一个描述符准备好可以读。一旦select返回,我们就使用FD_ISSET宏指令来确定哪个描述符准备好可以读了,并执行相应的操作。

select会修改参数fdset指向的fd_set,指明读集合的一个子集,称为准备好集合,该集合由读集合中准备好可以读的描述符组成。所以,每次调用select时都要更新读集合。

I/O多路复用的优劣:

(1)优点:

  • ​ 比基于进程的设计给了程序员更多的对程序行为的控制。
  • ​ 运行于单一进程的上下文中,因此每个逻辑流都能访问该进程的全部地址空间,这使得流之间的数据共享变得容易。

(2)缺点:

  • ​ 编码复杂。
  • ​ 只要某个逻辑流正忙于读一个文本行,其他逻辑流就不可能有进展。(本质上是阻塞的)
  • ​ 不能充分利用多核处理器。

基于线程的并发编程

线程就是运行在进程上下文中的逻辑流。线程由内核自动调度,每个线程都有它自己的线程上下文,包括一个唯一的整数线程id、栈、栈指针、程序计数器、通用目的寄存器和条件码(CPU根据运算结果由硬件设置的位,体现当前指令执行结果的各种状态信息)。所有运行在一个进程里的线程共享该进程的整个虚拟地址空间。

基于线程的并发服务器整体结构类似于基于进程的设计。主线程不断地等待连接请求,然后创建一个对等线程处理请求。

线程执行模型如下:

在这里插入图片描述
主线程与对等线程的区别仅在于它总是进程中第一个运行的线程。

C程序提供一个标准接口来处理线程,即Posix线程(Pthreads)。可以使用这个接口来创建、终止、回收、分离以及初始化线程。线程的代码和本地数据被封装在一个线程例程中。

int pthreads_create(pthread_t *tid, pthread_attr_t *attr, func *f, void *arg);
void pthread_exit(void *thread_return); //终止自己
int pthread_cancel(pthread_t pid); //终止别人
int pthread_join(pthread_t tid, void **thread_return); //回收已终止线程占用的所有内存资源
int pthread_detach(pthread_t tid); //分离线程
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));

在任何一个时间点上,线程是可结合的(joinable)或者是分离的(detached)。一个可结合的线程能够被其他线程收回和杀死。在被其他线程回收之前,它的内存资源(例如栈)是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的,它的内存资源在它终止时由系统自动释放。

线程的优劣:

(1)优点:

  • ​ 共享同一个虚拟地址空间,流之间的数据共享容易
  • ​ 能够充分利用多核处理器

(2)缺点:由于共享变量的存在,需要进行线程同步。

共享变量

当一个变量的一个实例被一个以上的线程引用时,它就被称为共享变量。共享变量会引入同步错误,这是由于每个并发执行定义了不同线程中的指令的某种全序(或者交叉),而这些顺序中的一些将会产生正确结果,但是其他的不会,并且我们是没有办法预测操作系统是否选择了正确的顺序。

互斥

对于线程i,操作共享变量内容的指令构成了一个(关于该共享变量)的临界区,这个临界区不能与其他线程的临界区交替执行,即每个线程在执行指令时拥有对共享变量的互斥的访问。

在这里插入图片描述
不安全区就是线程1和线程2同时操作共享变量内容的区域。为了保证总是有一条安全的轨迹线,我们必须以某种方式同步线程

信号量

信号量s是具有非负整数值的全局变量,只能由两种特殊的操作来处理,这两种操作称为P和V。

P(s):如果s是非0的,P操作使s减1,并且立即返回。如果s为0,则挂起这个线程,直到s变为非0。

V(s):V操作将s加1,如果有任何线程阻塞在P操作等待s变成非0,那么V操作会重启这些线程中的一个。然后执行P操作。

Posix标准定义了许多操作信号量的函数:

int sem_init(sem_t *sem, 0, unsigned int value);//将信号量初始化为value
int sem_wait(sem_t *s);//P操作
int sem_post(sem_t *s);//V操作
利用信号量实现互斥

基本思想:将每个共享变量(或者一组相关的共享变量)与一个信号量s(初始为1)联系起来,然后用P(s)和V(s)操作将相应的的临界区包围起来。

以提供互斥为目的的二元信号量常常也称为互斥锁(mutex),此时P操作称为给互斥锁加锁,V操作为给互斥锁解锁,加锁后未解锁的线程称为占用这个互斥锁。

利用信号量来调度共享资源

在这种场景中,一个线程通过信号量来通知另一个线程,程序状态中的某个变量为真了。下面为2个经典例子:

(1)生产者-消费者问题

在这里插入图片描述

需要考虑的问题:1、互斥访问缓冲区;2、调度对缓冲区的访问,当缓冲区为满时,生产者线程不可操作,当缓冲区为空时,消费者线程不可操作。

解决方法:定义三个信号量mutex、slots和items分别用于提供互斥的缓冲区访问、记录空槽位和可用项目的数量。

(2)读者-写者问题

只读对象的线程叫做读者,它可以与无限个其他读者共享对象;修改对象的线程叫做写者,它必须独占对对象的访问。

基于读者和写者的优先级,该问题又有几个变种:读者优先和写者优先。

定义readcnt共享变量记录当前读者数量,信号量mutex和w分别用来保护对共享变量readcnt的访问以及控制对访问共享对象的临界区的访问。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值