苏格拉底挑战
第四章 并发编程
一、知识点归纳
(一)线程
1.线程的原理
2.线程的优点
-
(1)线程创建和切换速度更快
-
(2)线程的响应速度更快
-
(3)线程更适合并行计算
3.线程的缺点
-
(1)由于地址空间共享,线程需要来自用户的明确同步。
-
(2)许多库函数可能对线程不安全。
-
(3)在单CPU系统上,使用线程解决问题实际上要比使用顺序程序慢,这是由在运行时创建线程和切换上下文系统开销造成的。
(二)线程操作
(三)线程管理函数
Pthread 库提供了用于线程管理的以下 API。
pthread_create(thread, attr, function, arg): create thread
pthread_exit(status) : terminate thread
pthread_cancel(thread) : cancel thread
pthread_attr_init(attr) : initialize thread attributes
pthread_attr_destroy(arr) : destroy thread attributes
1.创建线程
使用 pthread_create()
函数创建线程。
int pthread_create(pthread_t *pthread_id, pthread_attr_t *attr, void *(*func)(void *),void *arg);
如果成功则返回0,如果失败则返回错误代码。pthread_create()
函数的参数为
- pthread_id 是指向 pthread_t 类型变量的指针。它会被操作系统内核分配的唯一线程ID填充。
2.线程ID
3.线程终止
4.线程连接
(四)线程示例程序(见实践内容)
1.用线程计算矩阵的和
2.用线程快速排序
(五)线程同步
- 竞态条件:当多个线程试图修改同一共享变量或数据结构时,修改结果取决于线程的执行顺序。
1.互斥量
最简单的同步工具是锁,它允许执行实体仅在有锁的情况下才能执行。在 Pthread 中,锁被称为互斥量,意思是相互排斥。互斥变量是用 pthread_mutex_t 类型声明,在使用之前必须对它们进行初始化。有两种方法可以初始化互斥量。
- 一种是静态方案,如:
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER
定义互斥量 m,并使用默认属性对其进行初始化。 - 另一种是动态方案,使用
pthread_mutex_init()
函数,可通过 attr 参数设置互斥属性,如:pthread_mutex_init(pthread_mutex_t *m, pthread_mutexattr_t, *attr);
通常,attr 参数可以设置为 NULL,作为默认属性。
2.死锁预防
互斥量使用封锁协议。如果某线程不能获取互斥量,就会被阻塞,等待互斥量解锁后再继续。在任何封锁协议中,误用加锁可能会产生一些问题。最常见和突出的问题是死锁。死锁是一种状态,在这种状态下,许多执行实体相互等待,因此都无法继续下去。
与竞态条件类似,死锁决不能存在于并发程序中。有许多方法可以解决可能的死锁问题,其中包括死锁预防、死锁规避、死锁检测和恢复等。在实际系统中,唯一可行的方法是死锁预防,试图在设计并行算法时防止死锁的发生。一种简单的死锁预防方法是对互斥量进行排序,并确保每个线程只在一个方向请求互斥量,这样请求序列中就不会有循环。
但是,仅使用单向加锁请求来设计每个并行算法是不可能的。在这种情况下,可以使用条件加锁函数 pthread_mutex_trylock()
来预防死锁。如果互斥量已被加锁,则 trylock()
函数会立即返回一个错误。在这种情况下,调用线程可能会释放它已经获取的一些互斥量以便进行退避,从而让其他线程继续执行。在上面的交叉加锁示例,我们可以重新设计一个线程,例如 T1,利用条件加锁和退避来预防死锁。
/*Thread T1*/
while(1){
lock(m1);
if(!trylock(m2))
unlock(m1);
else
break;
}
3.条件变量
4.生产者—消费者问题
5.信号量
- 信号量是进程同步的一般机制。
6.屏障
7.用并发线程解线性方程式
8.Linux 中的线程
与许多其他操作系统不同,Linux 不区分进程和线程。对于 Linux 内核,线程只是一个与其他进程共享某些资源的进程。在 Linux 中,进程和线程都是由 clone()
系统调用创建的,具有以下原型:
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg)
可以看出,clone()
更像是一个线程创建函数。它创建一个子进程来执行带有 child_stack
的函数fn(arg)
。flag 字段详细说明父进程和子进程共享的资源。