《深入理解计算机系统》读书笔记7--- 并发编程1

时间:2022-05-10 03:40:40

CSAPP介绍了三种并发编程技术:(1)进程(2)I/O多路复用(3)线程

还有对于多线程并发编程的一些问题的思考

本帖先介绍三种并发模型,下一贴介绍多线程并发注意事项。

(1)基于进程的并发编程

核心函数就是fork。用父进程不停接受socket连接,fork创建子进程,用子进程来服务每一个client。

主要流程就是在echo服务器的基础上。

1.server会while循环阻塞在connfd=accept()那里。每当请求到达接受后,就fork一个子进程。

2.子进程会一直在echo程序里面的while循环。echo程序见书P632页。

3.子进程在client端那边输入EOF或者关掉以后,会检测到然后也终止进程。我们用SIGCHLD信号+waitpid来回收僵死进程。


注意:①进程的特点在前面的帖子里面有讲到。包括:共享文件表,相同但独立的地址空间等。

②由于fork的子进程会继承父进程的文件表,所以需要先给子进程关闭listenfd描述符,对于父进程也要相应关掉connfd描述符。


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

核心函数就是select。select就是用内核来监听我们关心的事件,当有任意事件发生,select返回,我们可以检测到底是发生了什么事件,进行相应地处理。

其实I/O多路复用,本质上并没有实现并发,但是如果我们将每一次的事件控制在很小的粒度,然后一次又一次地去处理发生的事件,就相当于并发了。

主要流程就是

1.server会while循环阻塞在select函数,每当有我们关心的事件发生,select函数就返回。我们进行相应地处理。

处理包括:

2.若是有新连接请求到来,接受请求,并将新接受connfd的有信息到来加入监听事件集合。

3.若是有已连接connfd有信息到来,读取一行信息。若是读到了EOF,就关闭该connfd,并将该connfd从监听事件集合里面移除。


注意:①I/O多路复用在使用select时涉及到蛮多的细节,比如,当select返回需要去循环检测到底是哪一个事件发生,需要随时增加或者删除需要监听的事件,需要对于clientfd进行一定的管理,等等。具体可以参见Unix网络编程,或者man select。

②之前提到的细粒度的操作,具体地说,在基于进程的并发编程之中,我们采用了echo函数来反弹client的信息。

而echo函数是利用while循环rio_readlineb,直到遇到EOF,这样会使程序一直在while之中。

这里我们每次select返回,对于某一个connfd我们只会调用一次rio_readlineb来读取一行的信息,所以称为细粒度。

这个细粒度的好处当然是实现了并发,但是坏处是对于故意只发送部分文本然后就停止的恶意客户端攻击显得很脆弱。


(3)基于线程的并发编程

核心函数就是pthread_create()。它用来创建新的线程,指定线程的执行函数,向新线程传递参数,以及通过参数返回线程的tid。

主要流程是

1.server会进入while循环,阻塞在accept。将connfd传入新线程处理函数。

2.然后里面分离线程(分离线程使得该线程结束后由系统回收资源),echo进入循环状态。


注意:①线程的特点是,像进程一样由内核调度,分配一个整数ID来识别。

                                            像I/O复用一样,运行与单一的进程上下文中,共享同一个虚拟地址空间。

好处是非常容易进行同一进程的线程之间通信(传递信息)比如直接利用全局变量,局部static变量,指向同一局部变量的指针均可。

坏处是这种方便的线程间通信是很容易发生危险的。有同步错误,竞争,死锁这样的问题。

②多线程由于方便的共享问题,可能出现各种问题,其中的竞争,表示一个线程要使用一个东西,但是在还没使用之前就被另一个线程修改了。

这里可以用到malloc技术,将某个线程需要用的的东西,先存在一个唯一的地方,然后使用之后释放。