CSAPP-----并发编程

本节目录

1、基于进程的并发编程

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

3、基于线程的并发编程

4、多线程程序中地共享变量

5、用信号量同步线程

6、使用线程提高并行性

7、其他并发问题

8、小结 


        本系列文章的观点和图片均来自《深入理解计算机系统第3版》仅作为学习使用

        如果逻辑控制流在时间上重叠,那它们就是并发的。这种常见的现象称为并发。目前位置,我们主要将并发看作是一种操作系统内核用来运行多个应用程序的机制,但是并发不仅仅局限于内核,也可以在应用程序中扮演重要角色。使用应用级并发的应用程序称为并发程序,现代操作系统提供三种基本的构造并发程序的方法:  

        *进程。用这种方法,每个逻辑控制流就是一个进程,由内核来调度和维护,因为进程有独立的虚拟地址空间,想要和其他流通信,控制流必须使用某种显式地进程间通信(IPC)机制。

        *IO多路复用。在这种形式的并发编程中,应用程序在一个进程的上下文中显式地调度他们自己的逻辑流,逻辑流被模型化为状态机,数据到达文件描述符后,主程序显式地从一个状态到另一个状态。因为程序是一个单独的进程,所有流都共享一个地址空间。

        *线程。线程是与运行在单一进程上下文的逻辑流,由内核进行调度,你可以把线程看作其他两种方式的胡赫梯,像进程流一样由内核调度,而像IO多路复用流一样共享一个虚拟地址空间。

1、基于进程的并发编程

        构造并发程序最简单的方法就是用进程,使用很熟悉的函数,fork(创建新进程)、exec(加载程序)和waitpid(等待子进程终止)。例如,一个构造并发服务器的自然方法是,在父进程中接收客户端连接请求,然后创建一个新的子进程来为每个新客户端提供服务。假设我们有两个客户端和一个服务器,服务器正在监听一个描述符上的连接请求,现在假设服务器接受了客户端1的连接请求,并返回一个已连接描述符,如下图所示,

        

        在接受完连接请求后,服务器派生一个子进程,这个子进程获得服务器描述表的完整副本,子进程关闭他的副本中的监听描述符,而父进程中关闭它的已连接描述符的副本,因为不需要这些描述符,此时如下图,其中子进程正忙于为客户端提供服务。

       

        因为父子进程的已连接描述符都指向同一个文件表表项,所以父进程关闭它的已连接描述的副本是很重要的,否则,将永远不会释放已连接描述符的文件表条目,而引起内存泄漏。

        现在,假设在父进程为客户端1创建了子进程之后,它会接受一个新的客户端2的连接请求,并返回一个新的已连接描述符,如下图所示,然后父进程又派生一个子进程,这个子进程用这个新的已连接描述符为它的客户端提供服务,此时,父进程等待下一个连接请求,这两个子进程正并发的为各自的客户端提供服务。

        

    1.1 基于进程的并发服务器

        具体查看(https://blog.csdn.net/zl6481033/article/details/86650745

    1.2 进程的优劣

        对于在父子进程间共享状态信息,进程有一个非常清晰的模型,共享文件表,但是不共享用户地址空间。进程具有独立的地址空间既是优点也是缺点,这样一来,一个进程不可能不小心覆盖另一个进程的虚拟内存,这样就消除了很多比较麻烦的错误。这是一个很明显的优点。

        另一方面,独立的地址空间使得进程共享状态信息变得更加困难,为了共享信息,它们必须使用显式地IPC(进程间通信)机制。基于进程的设计的另一个缺点是,往往比较慢,因为进程控制和IPC的开销都很高。

        Unix IPC,waitpid和信号是基本的IPC机制,它们允许进程发送小消息到同一主机的其他进程,套接字接口是IPC的一种重要形式,它允许不同主机的进程交换任意的字节流,术语 IPC通常指所有允许进程和同一台主机上其他进程进行通信的技术。其中包括管道,先进先出,系统共享内存、系统信号量。

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

        假设编写一个echo服务器,它也能对用户从标准输入的交互命令做出响应,这样的情况下,服务器必须响应两个互相独立的I/O事件,1)网络客户端发起连接请求,2)用户在键盘上输入命令行。那么这两个事件没有那个选择是理想的,如果在accept中等待一个连接请求,那么就不能响应输入的命令,如果在read中等待一个输入命令,就不能响应任何连接请求。

        针对这种困境的一个解决办法就是I/O多路复用技术。基本思想就是使用select函数,要求内核挂起进程,只有在一个或多个I/O事件发生后,才将控制返回给应用程序。

        select函数

 #include <sys/time.h>
 #include <sys/types.h>
 #include <unistd.h>

//返回已准备好的描述符的非0的个数,出错返回-1
int select(int nfds, fd_set *readfds, fd_set *writefds,
              fd_set *exceptfds, struct timeval *timeout);
//处理描述符集合的宏
void FD_CLR(int fd, fd_set *set);    //清空set中fd的位
int  FD_ISSET(int fd, fd_set *set);  //判断set中fd位
void FD_SET(int fd, fd_set *set);    //将set中fd置位
void FD_ZERO(fd_set *set);           //清空set中所有位

        select函数处理类型为fd_set的集合,也叫描述符集合,逻辑上将描述符集合看成一个大小为n的位向量(bn-1,....b1,b0)。每个位对应一个描述符,当bk=1时,描述符k才表明是描述符集合的一个元素,只允许你对描述符集合做三件事,1)分配他们,2)将一个此种类型的变量赋值给另一个变量。3)用FD_ZERO、FD_SET、FD_CLR和FD_ISSET宏来修改和检查它们。

        针对我们的目的,select函数有两个输入,一个称为读集合的描述符集合(fdset)和该读集合的基数(n)。select函数会一直阻塞,直到读集合中至少有一个描述符准备好可以读,当且仅当一个从该描述符读取字节的请求时不会阻塞时,描述符k就表示准备好了可以读,select有一个副作用,它修改参数fdset指向的fd_set,指明读一个集合的子集,称为准备好集合,这个集合是由读集合中准备好可以读了的描述符组成的,该函数返回的值指明了准备好集合的基数,由于这个副作用必须在每次调用select时都更新读集合。

        具体实现参考(https://blog.csdn.net/zl6481033/article/details/86661315

    2.1 基于IO多路复用的并发事件驱动服务器

        IO多路复用可以用到并发事件驱动程序的基础,在事件驱动程序中某些事件会导致流向前推进,一般的思路是将逻辑

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值