第四章:并发编程
知识点归纳总结:
-
本章论述了并发编程,介绍了并行计算的概念,指岀了并行计算的重要性;比较了顺序算法与并行算法,以及并行性与并发性;解释了线程的原理及其相对于进程的优势;
-
通过示例介绍了 Pthread中的线程操作,包括线程管理函数,互斥量、连接、条件变量和屏障等线程同步工具;通过具体示例演示了如何使用线程进行并发编程,包括矩阵计算、快速排序和用并发线程求解线性方程组等方法;
-
解释了死锁问题,并说明了如何防止并发程序中的死锁问题;讨论了信号量,并论证了它们相对于条件变量的优点;
-
解释了支持Linux中线程的独特方式。编程项目是为了实现用户级线程。它提供了一个基础系统来帮助读者开始工作。这个基础系统支持并发任务的动态创建、执行和终止,相当于在某个进程的同一地址空间中执行线程。读者可通过该项目实现线程同步的线程连接、互斥量和信号量,并演示它们在并发程序中的用法,该编程项目会让读者更加深入地了解多任务处理、线程同步和并发编程的原理及方法。
其中让我最有收获的几个部分如下:
- 线程
- 线程操作
- 线程管理函数
- 线程示例程序
- 线程同步
- 进程管理的系统调用
- I/O重定向
- 管道
线程的优点与缺点:
优点:
(1)线程创建和切换速度更快:进程的上下文复杂而庞大。其复杂性主要来自管理进程映像的需要。例如,在具有虚拟内存的系统中。进程映像可能由叫作页面的许多内存单元组成。在执行过程中。有些页面在内存中,有些则不在内存中。操作系统内核必须使用多个页表和多个级别的硬件辅助来跟踪每个进程的页面,要想创建新的进程,操作系统必须为进 程分配内存并构建页表。若要在某个进程中创建线程,操作系统不必为新的线程分配内存和创建页表,因为线程与进程共用同一个地址空间。所以,创建线程比创建进程更快。另外,由于以下原因,线程切换比进程切换更快。进程切换涉及将一个进程的复杂分贞环境 替换为另一个进程的复杂分页环境,需要大量的操作和时间。相比之下,同一个进程中的线程切换要简单得多、也快得多,因为操作系统内核只需要切换执行点,而不需要更改进程映像。
(2)线程的响应速度更快:一个进程只有一个执行路径。当某个进程被挂起时,整个进程都将停止执行。相反,当某个线程被挂起时,同进程中的其他线程可以继续执行。这使得有多个线程的程序响应速度更快.例如,在一个多线程的进程中,当一个线程被阻塞以等待I/O时,其他线程仍可在后台进行计算。在有线程的服务器中,服务器可同时服务多个客户机。
(3)线程更适合并行计算:并行计算的目标是使用多个执行路径更快地解决问题。基于分治原则(如二叉树查找和快速排序等)的算法经常表现出高度的并行性,可通过使用并行或并发执行来提高计算速度。这种算法通常要求执行实体共享公用数据。在进程模型中,各进程不能有效共享数据,因为它们的地址空间都不一样。为了解决这个问题,进程必须使用进程间通信(IPC)来交换数据或使用其他方法将公用数据区包含到其地址空间中。相反,同一进程中的所有线程共享同一地址空间中的所有(全局)数据。因此,使用线程编写并行执行的程序比使用进程编写更简单、更自然。
缺点:
(1)由于地址空间共享,线程需要来自用户的明确同步。
(2)许多库函数可能对线程不安全,例如传统strtok()函数将一个字符串分成一连串令 牌。通常,任何使用全局变量或依赖于静态内存内容的函数.线程都不安全。为了使库函数 适应线程环境,还需要做大量的工作。
(3)在单CPU系统上,使用线程解决问题实际上要比使用顺序程序慢,这是由在运行 时创建线程和切换上下文的系统开销造成的。
线程管理函数:
pthread_create函数: 创建一个新的进程
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
Compile and link with -pthread.
pthread_exit函数:退出当前线程
void pthread_exit(void *retval);
pthread_join函数:以阻塞方式等待某一线程结束。
int pthread_join(pthread_t thread, void **retval);
pthread_cancel函数:结束某个线程,只是请求取消运行
int pthread_cancel(pthread_t thread);
pthread_self函数: 获取当前线程的TID
pthread_t pthread_self(void);
实践:
线程示例程序4.1:
#include <stdlib.h>
#include <pthread.h>
typedef struct{
int upperbound;
int lowerbound;
}PARM;
#define N 10
int a[N]={5,1,6,4,7,2,9,8,0,3};// unsorted data
int print(){//print current a[] contents
int i;
printf("[");
for(i=0;i<N;i++)
printf("%d ",a[i]);
printf("]\n");
}
void *Qsort(void *aptr){
PARM *ap, aleft, aright;
int pivot, pivotIndex,left, right,temp;
int upperbound,lowerbound;
pthread_t me,leftThread,rightThread;
me = pthread_self();
ap =(PARM *)aptr;
upperbound = ap->upperbound;
lowerbound = ap->lowerbound;
pivot = a[upperbound];//pick low pivot value
left = lowerbound - 1;//scan index from left side
right = upperbound;//scan index from right side
if(lowerbound >= upperbound)
pthread_exit (NULL);
while(left < right){//partition loop
do{left++;} while (a[left] < pivot);
do{right--;}while(a[right]>pivot);
if (left < right ) {
temp = a[left];a[left]=a[right];a[right] = temp;
}
}
print();
pivotIndex = left;//put pivot back
temp = a[pivotIndex] ;
a[pivotIndex] = pivot;
a[upperbound] = temp;
//start the "recursive threads"
aleft.upperbound = pivotIndex - 1;
aleft.lowerbound = lowerbound;
aright.upperbound = upperbound;
aright.lowerbound = pivotIndex + 1;
printf("%lu: create left and right threadsln", me) ;
pthread_create(&leftThread,NULL,Qsort,(void * )&aleft);
pthread_create(&rightThread,NULL,Qsort,(void *)&aright);
//wait for left and right threads to finish
pthread_join(leftThread,NULL);
pthread_join(rightThread, NULL);
printf("%lu: joined with left & right threads\n",me);
}
int main(int argc, char *argv[]){
PARM arg;
int i, *array;
pthread_t me,thread;
me = pthread_self( );
printf("main %lu: unsorted array = ", me);
print( ) ;
arg.upperbound = N-1;
arg. lowerbound = 0 ;
printf("main %lu create a thread to do QS\n" , me);
pthread_create(&thread,NULL,Qsort,(void * ) &arg);//wait for Qs thread to finish
pthread_join(thread,NULL);
printf ("main %lu sorted array = ", me);
print () ;
}