《Unix/Linux系统编程》第四章学习笔记
第四章 并发编程
1、并行计算导论
(1)顺序算法与并行算法
在描述顺序算法中,常用一个begin-end代码块列出算法。该代码块中的所有步骤都是通过某个任务依次执行的。
而并行算法中,cobegin-coend代码块指定并行算法的独立任务。每个任务都是并行执行的。
(2)并行性与并发性
理想情况下,并行算法中的所有任务应该同时实时执行。单在单CPU系统中,不能实现。因此,不同任务只能并发执行,并在逻辑上并行执行。
并发性是通过多任务处理来实现的。
2、线程
(1)原理
进程是独立的执行单元。所有进程均在内核模式或用户模式下执行。
线程是某进程统一地址空间上的独立执行单元。
当某进程开始时,就会执行该进程的主线程。主线程可能会创建其他线程。
线程共享进程的许多资源。如果一个线程被挂起,其他线程可以继续执行。
(2)优点
线程创建和切换速度更快:进程的上下文复杂而庞大。其复杂性主要来自管理进程映像的需要。例如,在具有虚拟内存的系统中。进程映像可能由叫作页面的许多内存单元组成。在执行过程中,有些页面在内存中,有些则不在内存中。操作系统内核必须使用多个页表和多个级别的硬件辅助来跟踪每个进程的页面。要想创建新的进程,操作系统必须为进程分配内存并构建页表。若要在某个进程中创建线程,操作系统不必为新的线程分配内存和创建页表。因为线程与进程共用同一个地址空间。所以创建线程比创建进程更快。另外,由于以下原因,线程切换比进程切换更快。进程切换涉及将一个进程的复杂分页环境替换为另一个进程的复杂分页环境,需要大量的操作和时间。相比之下。同一个进程中的线程切换要简单得多、也快得多,因为操作系统内核只需要切换执行点,而不需要更改进程映像。
线程的响应速度更快:一个进程只有一个执行路径。当某个进程被挂起时、整个进程都将停止执行。相反,当某个线程被挂起时,同一进程中的其他线程可以继续执行。这使得有多个线程的程序响应速度更快。例如,在一个多线程的进程中,当一个线程被阻塞以等待I/O时,其他线程仍可在后台进行计算。在有线程的服务器中,服务器可同时服务多个客户机。
线程更适合并行计算:并行计算的目标是使用多个执行路径更快地解决问题。基于分治原则(如二叉树查找和快速排序等)的算法经常表现出高度的并行性。可通过使用并行或并发执行来提高计算速度。这种算法通常要求执行实体共享公用数据。在进程模型中,各进程不能有效共享数据,因为它们的地址空间都不一样。为了解决这个问题,进程必须使用进程间通信(IPC)来交换数据或使用其他方法将公用数据区包含到其地址空间中。相反,同一进程中的所有线程共享同一地址空间中的所有(全局)数据。因此,使用线程编写并行执行的程序比使用进程编写更简单、更自然。
(3)缺点
由于地址空间共享,线程需要来自用户的明确同步。
许多库函数可能对线程不安全,例如传统 strtok()函数将一个字符串分成一连串令牌。通常,任何使用全局变量或依赖于静态内存内容的函数,线程都不安全。为了使库函数适应线程环境,还需要做大量的工作。
在单CPU系统上,使用线程解决问题实际上要比使用顺序程序慢,这是由在运行时创建线程和切换上下文的系统开销造成的。
3、线程管理函数
(1)创建线程
int pthread_create(pthread_t *thread_id,pthread_attr_t *attr,void *(*func)(void*), void *arg);
int pthread_equal(pthread_t t1,pthread_t t2);
如果是不同线程,返回0;否则,返回非0。
(3)线程终止
void pthread_exit(void *status);
0退出值表示正常终止,否则为异常终止。
(4)线程连接
int pthread_join (pthread_t thread, void **status ptr);
4、线程同步
(1)互斥量
最简单的同步工具是锁,它允许执行实体仅在有锁的情况下才能继续执行。在Pthread中,锁被称为互斥量,意思是相互排斥。在使用之前必须对他们进行初始化。
-
静态方法:定义互斥量m,并使用默认属性对其进行初始化。
pthreaa_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
-
动态方法: 使用pthread_ mutex _init()函数,可通过attr参数设置互斥属性。
(2)死锁预防
简单的死锁预防是对互斥量进行排序,并确保每个线程只在一个方向请求互斥量,这样请求序列中就不会有循环。
(3)条件变量
条件变量提供线程协作的方法。同样需要初始化,且方法同互斥量相同。
(4)信号量
信号量是进程同步的一般机制。比条件变量多一个计数器。
(5)屏障
屏障是线程的集合点。当所有线程到达屏障时,所有线程重新开始执行。