第十二章 并发编程
第十二章 并发编程
概述:如果逻辑控制流在时间上重叠,那么他们就是并发的
应用级并发在其他情况下的应用
- 访问慢速I/O设备
- 与人交互
- 通过推迟工作以降低延迟
- 服务多个网络客户端
- 在多核机器上进行并发计算
12.1基于进程的并发编程
构造并发程序最简单的方法就是用进程
常用函数
- fork
- exec
- waitpid
原理
在父进程中接受客户端连接请求,然后创建一个新的子进程来为每个新客户端提供服务。
12.1.1基于进程的并发服务器
关于服务器需要说明的地方、
- 要包括一个sigchld处理程序,来回收僵死程序
- 父子进程必须关闭他们各自的connfd
- 直到父子进程的connfd都关闭了,到客户端的链接才会终止
12.1.2关于进程的优劣
1.优点:防止虚拟存储器被错误覆盖
2.缺点:开销高,共享状态信息才需要IPC机制
12.2基于I/O多路复用的并发编程
使用I/O多路复用的并发编程的原因
同时响应下面两个请求
- 网络客户端发起连接请求
- 用户在键盘上输入命令行
原理
就是使用select函数要求内核挂起进程,只有在一个或多个I/O事件发生后,才将控制返回给应用程序。
12.2.1基于I/O多路复用的并发事件驱动服务器
事件驱动程序:将逻辑流模型化为状态机。
状态机:
- 状态
- 输入事件
- 转移
12.2.2 I/O多路复用技术的优劣
1.优点
- 相较基于进程的设计,给了程序员更多的对程序程序的控制
- 运行在单一进程上下文中,所以每个逻辑流都可以访问该进程的全部地址空间,共享数据容易实现
- 可以使用GDB调试
- 高效
2.缺点 - 编码复杂
- 不能充分利用多核处理器
12.3基于线程的并发编程
是以上两种方法的混合
- 线程有内核自动调度
- 多个线程运行在单一进程的上下文中
12.3.2posix线程
void *thread(void *vargp);
int main()
{
pthread_t tid;
Pthread_create(&tid, NULL, thread, NULL);
Pthread_join(tid,NULL);
exit(0);
}
void *thread(void *vargp)
{
printf(“Hello , world\n”);
return NULL;
}
12.3.3创建线程
线程通过调用pthread_create函数来创建其它线程
#include <pthread.h>
typedef void *(func)(void *);
int pthread_create(pthread_t *tid, pthread_attr_t *attr, func *f, void *arg);
12.3.4终止线程
- 顶层的线程例程返回
- 调用pthread_exit函数
- 如果主线程调用,会先等待所有其他对等线程终止,再终止主线程和整个进程,返回值为pthread_return
某个对等线程调用Unix的exit函数,会终止进程与其相关线程 - 另一个对等线程通过以当前线程ID作为参数调用pthread_cancle来终止当前线程
12.4多线程程序中的共享变量
一个变量是共享的,当且仅当多个线程引用这个变量的某个实例。
12.5用信号量同步线程
第一个 CreateSemaphore
函数功能:创建信号量
函数原型:
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
LONG lInitialCount,
LONG lMaximumCount,
LPCTSTR lpName
);
函数说明:
第一个参数表示安全控制,一般直接传入NULL。
第二个参数表示初始资源数量。
第三个参数表示最大并发数量。
第四个参数表示信号量的名称,传入NULL表示匿名信号量。
第二个 OpenSemaphore
函数功能:打开信号量
函数原型:
HANDLE OpenSemaphore(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName
);
函数说明:
-
第一个参数表示访问权限,对一般传入SEMAPHORE_ALL_ACCESS。详细解释可以查看MSDN文档。
-
第二个参数表示信号量句柄继承性,一般传入TRUE即可。
-
第三个参数表示名称,不同进程中的各线程可以通过名称来确保它们访问同一个信号量。
第三个 ReleaseSemaphore
函数功能:递增信号量的当前资源计数
函数原型:
BOOL ReleaseSemaphore(
HANDLE hSemaphore,
LONG lReleaseCount,
LPLONG lpPreviousCount
);
函数说明:
-
第一个参数是信号量的句柄。
-
第二个参数表示增加个数,必须大于0且不超过最大资源数量。
-
第三个参数可以用来传出先前的资源计数,设为NULL表示不需要传出。
由于信号量是内核对象,因此使用CloseHandle()就可以完成清理与销毁了。
12.6使用线程提高并行性
四类线程不安全函数:
-
不保护共享变量的函数
-
保持跨越多个调用的状态的函数
-
返回指向静态变量的指针的函数
-
调用线程不安全函数的函数
可重入性:当函数被多个线程调用,不会引用任何共享数据
显式可重入:所有函数参数都是传值传递,所有数据引用都是本地自动栈变量
隐式可重入:显式可重入加上一些参数是引用传递(指向非共享数据的指针)
竞争:当一个程序的正确性依赖于,一个线程要在另一个线程到达y点之前到达它的控制流中的x点,就会发生竞争
线程化的程序必须对任何可行的轨迹线都正确工作
死锁:一组线程被阻塞了,等待一个永远也不会为真的条件。
死锁不总是可以预测的,而错误常常不可重复。
学习心得
这周的内容和操作系统的内容有相似的地方,都是从分析并发执行的程序的状况。操作系统对于进程的调用,
进程的死锁情况的解决以及更多别的问题讲的更详细。但是本书更注重从代码的角度讲述并发编程的问题,所以通过
学习这门课,使我从不同的角度了解了操作系统。
参考文献:
- 深入理解计算机系统第十二章
- 20135202闫佳歆同学的博客
- zhanghaodx082的专栏http://blog.csdn.net/zhanghaodx082/article/details/11932865