Linux 系统编程|线程
Linux 系统编程
第六章 线程
6.1 线程的基本概念
一、定义
- 进程是资源管理的最小单位,线程是程序执行的最小单位。
- 每个进程拥有自己的数据段、代码段和堆栈段。线程是轻型的进程,它包含独立的栈和 CPU 寄存器状态。线程是进程的一条执行路径,每个线程共享其所附属的进程的所有资源,包括打开的文件、内存、信号标识和动态分配的内存等。
- 线程比进程小得多,花费更少的 CPU 资源。
- 在操作系统设计上,从进程演化出线程,最主要的目的就是更好地支持多处理器,并且减小进程上下文切换的开销。
默认情况下,一个进程中只有一个线程(主控线程/主线程)。通过主控线程,可以创建出其他若干子线程。主控线程和子线程都隶属于当前的进程。当系统给一个进程分配一定的时间片,这些时间会分配给其中的线程,但是在同一时间,只能有一个线程在执行,具体的执行根据系统的调度,将对应线程从就绪状态变为运行状态。
即,系统先调度哪一个进程执行,再调度进程中哪一个线程执行。
进程和线程的关系
线程属于进程。线程运行在进程空间内。统一进程所产生的线程共享同一用户内存空间,当进程退出时该进程所产生的所有线程都会被强制退出。一个进程至少需要一个线程作为它的指令执行体,进程管理着资源(CPU、内存、文件等),而线程将被分配到某个 CPU 上执行。
二、分类
线程按照其调度者可以分为用户级线程和内核级线程。
- 用户级线程:主要解决上下文切换问题,其调度过程由用户决定。
- 内核级线程:由内核调度机制实现。
现代多数操作系统都采用用户级线程和内核级线程并存的方法。用户级线程要绑定内核级线程运行。也就是说,用户可以决定用户级线程何时创建、何时终止等,但具体有没有执行,需要由系统调度其所绑定的内核级线程决定。
一个进程中的内核级线程会分配到固定的时间片,用户级线程分配的时间片以内核级线程为准。
默认情况下用户级线程和内核级线程是一对一关系,也可以多对一,但是实时性较差。
当 CPU 分配给线程的时间片用完后线程没有执行完毕,此时线程会从运行状态返回到就绪状态,将 CPU 让给其他线程。
三、Linux 线程实现
Linux 一般采用 pthread 线程库实现线程的访问与控制。此库由 POSIX 提出,具有良好的可移植性。
Linux 线程程序编译需要在 gcc 上链接 pthread 库。例如
1 gcc -o thread_test thread_test.c -lpthread
线程标识
- 每个进程内部的不同线程都有自己的唯一标识
- 线程标识只在其所属的进程环境中有效
- 线程标识是
pthread_t
类型
1 |
|
6.2 线程编程
一、线程的创建
1 |
|
举例应用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
typedef struct {
char name[20];
int time;
int start;
int end;
} RaceArg;
void* th_fn(void *arg)
{
RaceArg *r = (RaceArg *)arg;
int i = r->start;
for(; i <= r->end; i++) {
printf("%s(%lx) running %d\n", r->name, pthread_self(), i);
usleep(r->time);
}
return (void *)0;
}
int main()
{
int err;
pthread_t rabbit, turtle;
RaceArg r_a = {"rabbit", (int)(drand48() * 100000000), 20, 50};
RaceArg t_a = {"turtle", (int)(drand48() * 100000000), 10, 60};
if ((err = pthread_create(&rabbit, NULL, th_fn, (void *)&r_a)) != 0) {
perror("pthread_create error");
exit(1);
}
if ((err = prthread_create(&turtle, NULL, th_fn, (void *)&t_a)) != 0) {
perror("pthread_create error");
exit(1);
}
/*
主控线程可能在创建创建 rabbit 和 turtle 继续运行
从而导致还没赛跑就结束了
可以使用 pthread_join 函数优先运行两个子线程,而把主控线程阻塞
*/
pthread_join(rabbit, NULL);
pthread_join(turtle, NULL);
printf("control thread id: %lx\n", pthread_self());
printf("finish\n");
return 0;
}
二、线程的终止
1 |
|
pthread_cancel
线程可以被同一进程的其他线程取消,
tid
为被终止线程的线程标识符。pthread_exit
线程退出时使用此函数,是线程的主动行为(相当于直接
return
)。参数retval
是线程的返回值,可由其他函数或pthread_join
函数检测获取。由于一个进程的多个线程共享数据段,因此通常在线程退出后,其所占用的资源并不会随着线程结束而释放。一般地,通过pthread_join
执行的进程,在结束后其资源会被释放。pthread_join
参数
thread_return
为用户自定义的指针,用来存放返回值。
在线程执行函数中执行
exit()
会导致进程终止。举例说明:
pthread_join
接收返回值的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
typedef struct {
int d1;
int d2;
} Arg;
void* th_fn(void *arg)
{
Arg *r = (Arg *)arg;
return (void *)(r->d1 + r->d2);
}
int main()
{
int err;
pthread_t th;
Arg r = {20, 50};
if ((err = pthread_create(&th, NULL, th_fn, (void *)&r)) != 0) {
perror("pthread_create error");
exit(1);
}
int *result;
pthread_join(th, (void **)&result);
printf("result is %d\n", (int)result); // 指针本身存的就是值
return 0;
}
三、线程的清理
1 |
|
四、线程属性
线程属性的初始化和销毁
1 |
|
设置和获得分离属性
1 |
|
- 以默认方式启动的线程,在结束后不会自动释放系统资源,除非在主控线程调用了
pthread_join
- 以分离状态启动的线程,在结束后会自动释放系统资源,但这类线程不能通过
pthread_join
启动 - 分离属性一般用在网络通讯中
6.3 线程互斥和同步
(一) 基本概念与锁的使用
一、线程的同步和互斥
- 线程同步
- 是一个宏观概念,在微观上包含线程的相互排斥和线程先后执行的约束问题。
- 解决同步的方式
- 条件变量
- 线程信号量
- 线程互斥
- 线程执行的相互排斥
- 解决互斥的方式
- 互斥锁
- 读写锁
- 线程信号量
二、互斥锁
互斥锁又称互斥量,是一种简单的加锁方式,用于控制对共享资源的访问。
- 一把锁,一般和一个共享资源绑定,或者设置为全局变量。
- 在同一时刻,只能有一个线程拥有某个互斥锁,具备上锁状态的线程能够对共享资源进行访问。
- 若其他线程希望上锁一个已经被上锁的共享资源,则该线程挂起,直到上锁的线程释放了互斥锁为止。
互斥锁的类型
pthread_mutex_t
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *mutexattr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
/*
成功返回 0,否则返回错误编号
mutexattr - 互斥锁的创建方式
PTHREAD_MUTEX_INITIALER / NULL 创建标准/默认互斥锁
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP 创建递归互斥锁
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP 创建检错互斥锁
[用法1]
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
[用法2]
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
*/上锁和解锁
1
2
3
4
5
6
7
8
9
10
11
12
int pthread_mutex_lock(pthread_mutex_t *mutex);
// 上锁,如果无法上锁则阻塞
int pthread_mutex_trylock(pthread_mutex_t *mutex);
// 上锁,如果无法上锁返回错误信息
int pthread_mutex_unlock(pthread_mutex_t *mutex);
// 释放锁
/*
返回值:成功返回 0,否则返回错误编号
对于共享资源操作的代码,在前面应进行上锁,在后面应释放锁。其间的代码称为临界区。
*/互斥锁属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr,
int *restrict type);
int pthread_mutexattr_settype(const pthread_mutexattr_t *attr, int type);
/*
返回值:成功返回 0,否则返回错误编号
type - 互斥锁类型
PTHREAD_MUTEX_NORMAL / PTHREAD_MUTEX_DEFAULT
标准 / 默认互斥锁 第一次上锁成功,第二次上锁阻塞
PTHREAD_MUTEX_RECURSIVE
递归互斥锁 第一次上锁成功,接下来上锁还成功,内部计数
PTHREAD_MUTEX_ERRORCHECK
检错互斥锁 第一次上锁成功,第二次上锁出错
*/
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr,
int *restrict pshared);
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);
/*
返回值:成功返回 0,否则返回错误编号
pshared - 进程共享属性
PTHREAD_PROCESS_PRIVATE(默认) 锁只能用在一个进程内
PTHREAD_PROCESS_SHARED 锁可以用在不同进程间
*/举例:从命令行获取锁的类型,进行两次连续上锁测试。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
int main(int argc, char *argv[])
{
pthread_mutex_t mutex;
if (argc < 2) {
fprintf(stderr, "-usage: %s [error|normal|recursive]\n", argv[0]);
exit(1);
}
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
if (!strcmp(argv[1], "error")) {
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
} else if (!strcmp(argv[1], "normal")) {
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
} else if (!strcmp(argv[1], "recursive")) {
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
} else {
fprintf(stderr, "unknown type\n");
exit(1);
}
pthread_mutex_init(&mutex, &attr);
if (pthread_mutex_lock(&mutex) != 0) {
printf("first lock failure\n");
} else {
printf("first lock success\n");
}
if (pthread_mutex_lock(&mutex) != 0) {
printf("second lock failure\n");
} else {
printf("second lock success\n");
}
pthread_mutexattr_destroy(&attr);
pthread_mutex_destroy(&mutex);
return 0;
}
三、读写锁
线程使用互斥锁缺乏读并发性。当读操作较多,写操作较少时,可以使用读写锁提高读并发性。
读写锁数据类型
pthread_rwlock_t
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
// attr 为读写锁属性,一般用 NULL
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
/*
返回值:成功返回 0,否则返回错误编号
*/
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
// 上读锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
// 上写锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
// 释放锁
/*
返回值:成功返回 0,否则返回错误编号
*/读写锁加锁特性
- 先上读锁
- 再上读锁:成功
- 再上写锁:阻塞
- 先上写锁
- 再上读锁:失败
- 再上写锁:失败
- 先上读锁
(二) 条件变量
一、条件变量概念
- 互斥锁的缺点是其只有两种状态,即锁定和非锁定。
- 条件变量允许通过线程阻塞和发送信号的机制,弥补互斥锁的不足。
- 条件变量内部维护了一个等待队列,放置等待中的线程。由于等待队列本身仍被多个线程共享,因而它需要互斥锁保护。
- 条件变量允许线程等待特定条件发生,当条件不满足时,线程先进入阻塞状态。一旦其他某个线程改变了条件,可以唤醒一个或多个等待着的线程。这个判断条件一般由用户给出。
二、条件变量的使用
条件变量的类型
pthread_cond_t
相关函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int pthread_cond_init(pthread_cond_t *restrict cond,
pthread_condattr_t *restrict attr); // 属性一般用 NULL
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
// 把自己放入等待队列,并用锁 mutex 保护队列
int pthread_cond_timewait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict timeout);
// 若超时自动返回
struct timespec
{
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
int pthread_cond_signal(pthread_cond_t *cond); // 通知单个线程
int pthread_cond_broadcast(pthread_cond_t *cond); // 通知所有线程
举例应用:创建一个计算线程和一个获取结果的线程,必须先计算,再获取结果(同步问题)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
typedef struct
{
int res;
int is_wait; // 如果有多个读者,可以用计数器实现
pthread_cond_t cond;
pthread_mutex_t mutex;
} Result;
void* cal_fn(void *arg)
{
Result *r = (Result *)arg;
int sum = 0;
for (int i = 1; i <= 100; i++) sum += i;
r->res = sum;
// r->is_wait 是共享资源,必须通过锁实现互斥
pthread_mutex_lock(&r->mutex);
while (!r->is_wait) { // 如果获取结果的线程尚未准备好
pthread_mutex_unlock(&r->mutex); // 先释放锁,给对方操作共享资源的机会
usleep(100); // 睡眠,给对方操作共享资源的时间
pthread_mutex_lock(&r->mutex); // 继续上锁
}
pthread_mutex_unlock(&r->mutex);
pthread_cond_broadcast(&r->cond); // 跳出循环表示对方已经准备好,进行通知
return (void *)0;
}
void* get_fn(void *arg)
{
Result *r = (Result *)arg;
pthread_mutex_lock(&r->mutex); // 操作共享资源前先上锁
r->is_wait = 1;
pthread_cond_wait(&r->cond, &r->mutex); // 等待函数应放在 unlock 之前
pthread_mutex_unlock(&r->mutex);
int res = r->res;
printf("0x%lx get result: %d\n", pthread_self(), res);
return (void *)0;
}
int main()
{
int err;
pthread_t cal, get;
Result r;
r.is_wait = 0;
pthread_cond_init(&r.cond, NULL);
pthread_mutex_init(&r.mutex, NULL);
if ((err = pthread_create(&cal, NULL, cal_fn, (void *)&r)) != 0) {
perror("pthread create error");
}
if ((err = pthread_create(&get, NULL, get_fn, (void *)&r)) != 0) {
perror("pthread create error");
}
pthread_join(cal, NULL);
pthread_join(get, NULL);
pthread_cond_destroy(&r.cond);
pthread_mutex_destroy(&r.mutex);
return 0;
}
pthread_cond_wait
的执行流程
1
2
3
4
5
6
7
8
9
10 pthread_cond_wait(cond, mutex)
{
1) unlock(&mutex) // 先释放锁
2) lock(&mutex)
3) 将自己插入到 cond 的等待队列中
4) unlock(&mutex)
5) 自己阻塞,等待其他线程唤醒自己
6) 唤醒后 lock(&mutex) // 这里有可能被阻塞
7) 从 cond 的等待队列中删除自己 // 所以 wait 应该放在 unlock 之前
}
线程的状态转换
举例应用:读者和写者问题(两个线程的相互通知)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
typedef struct
{
int val;
pthread_cond_t reader_cond;
pthread_mutex_t reader_mutex;
int reader_wait;
pthread_cond_t writer_cond;
pthread_mutex_t writer_mutex;
int writer_wait;
} Storage;
void set_data(Storage *s, int data)
{
s->val = data;
}
int get_data(Storage *s)
{
return s->val;
}
void* w_fn(void *arg)
{
Storage *s = (Storage *)arg;
// 循环写数
for (int i = 1; i <= 100; i++) {
set_data(s, i + 20);
printf("0x%lx(%d) write data: %d\n", pthread_self(), i, i + 20);
// 等待读者准备好
pthread_mutex_lock(&s->reader_mutex);
while (!s->reader_wait) {
pthread_mutex_unlock(&s->reader_mutex);
sleep(1);
pthread_mutex_lock(&s->reader_mutex);
}
// 重置读者准备状态,通知读者
s->reader_wait = 0;
pthread_mutex_unlock(&s->reader_mutex);
pthread_cond_broadcast(&s->reader_cond);
// 等待读者读完后通知自己
pthread_mutex_lock(&s->writer_mutex);
s->writer_wait = 1;
pthread_cond_wait(&s->writer_cond, &s->writer_mutex);
pthread_mutex_unlock(&s->writer_mutex);
}
return (void *)0;
}
void* r_fn(void *arg)
{
Storage *s = (Storage *)arg;
// 循环读数
for (int i = 1; i <= 100; i++) {
// 等待写者写完后通知自己
pthread_mutex_lock(&s->reader_mutex);
s->reader_wait = 1;
pthread_cond_wait(&s->reader_cond, &s->reader_mutex);
pthread_mutex_unlock(&s->reader_mutex);
int data = get_data(s);
printf("0x%lx(%d) read data: %d\n", pthread_self(), i, data);
// 等待写者准备好
pthread_mutex_lock(&s->writer_mutex);
while (!s->writer_wait) {
pthread_mutex_unlock(&s->writer_mutex);
sleep(1);
pthread_mutex_lock(&s->writer_mutex);
}
// 重置写者准备状态,通知写者
s->writer_wait = 0;
pthread_mutex_unlock(&s->writer_mutex);
pthread_cond_broadcast(&s->writer_cond);
}
return (void *)0;
}
int main()
{
int err;
pthread_t rth, wth;
Storage s;
s.reader_wait = s.writer_wait = 0;
pthread_mutex_init(&s.reader_mutex, NULL);
pthread_mutex_init(&s.writer_mutex, NULL);
pthread_cond_init(&s.reader_cond, NULL);
pthread_cond_init(&s.writer_cond, NULL);
if ((err = pthread_create(&rth, NULL, r_fn, (void*)&s)) != 0) {
perror("pthread create error");
}
if ((err = pthread_create(&wth, NULL, w_fn, (void *)&s)) != 0) {
perror("pthread create error");
}
pthread_join(rth, NULL);
pthread_join(wth, NULL);
pthread_mutex_destroy(&s.reader_mutex);
pthread_mutex_destroy(&s.writer_mutex);
pthread_cond_destroy(&s.reader_cond);
pthread_cond_destroy(&s.writer_cond);
return 0;
}
(三) 线程信号量
信号量本质上是一个非负整数计数器,可以代表共享资源的数目,通常用于控制对共享资源的访问。
信号量可以实现线程的同步和互斥。
信号量数据类型
sem_t
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int sem_init(sem_t *sem, int pshared, unsigned value);
// pshared 为是否在进程间共享的标志,0 为不共享,1 为共享
// value 为信号量初值
int sem_destroy(sem_t *sem);
int sem_post(sem_t *sem);
// 对信号量作[加 1 操作],相当于 V(1) 操作
int sem_wait(sem_t *sem);
// 对信号量作[减 1 操作],相当于 P(1) 操作
// 若减 1 后信号量的值小于 0 则阻塞当前线程
int sem_trywait(sem_t *sem);
// sem_wait 的非阻塞版本
举例应用:通过两个信号量实现三个线程的同步问题(打印 cba)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
sem_t sem1, sem2;
void* a_fn(void *arg)
{
sem_wait(&sem1);
putchar('a');
putchar('\n');
return (void *)0;
}
void* b_fn(void *arg)
{
sem_wait(&sem2);
putchar('b');
sem_post(&sem1);
return (void *)0;
}
void* c_fn(void *arg)
{
putchar('c');
sem_post(&sem2);
return (void *)0;
}
int main()
{
pthread_t a, b, c;
sem_init(&sem1, 0, 0);
sem_init(&sem2, 0, 0);
pthread_create(&a, NULL, a_fn, (void *)0);
pthread_create(&b, NULL, b_fn, (void *)0);
pthread_create(&c, NULL, c_fn, (void *)0);
pthread_join(a, NULL);
pthread_join(b, NULL);
pthread_join(c, NULL);
sem_destroy(&sem1);
sem_destroy(&sem2);
return 0;
}
(四) 死锁
- 概念:死锁指的是多个线程在运行过程中因争夺资源造成的僵局,当程序处于死锁状态,若无外力作用则无法继续运行下去。
- 死锁产生的条件
- 互斥条件:线程对资源存在排他性使用
- 请求和保持条件:线程既占有某一资源不放,又请求某一新的资源
- 不剥夺条件:线程已经获得的资源只能由自己释放
- 环路等待条件:设存在线程集合 $\{t_1,t_2,\ \dots\ ,t_n\}$,$t_1$ 等待 $t_2$ 占有的资源,$t_2$ 等待 $t_3$ 占有的资源,…… ,$t_n$ 等待 $t_1$ 占有的资源
- 死锁的解决方法
- 破坏死锁产生的条件,尤其是加锁的顺序(一般按照相同的顺序加锁)
- 设置加锁时限(到达某一时间后放弃对某资源的请求,并主动释放自己占有的资源)(也即调用非阻塞版本的上锁函数)
6.4 线程和信号
定时器是进程资源,进程中所有的线程共享相同的定时器。子线程调用
alarm()
产生的 SIGALRM 信号发送给主控线程。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
void sig_handler(int sig)
{
printf("pthread id in the sig_handler: 0x%lx\n", pthread_self());
if (sig == SIGALRM) {
printf("time out\n");
}
alarm(2); // 循环发信号
}
void* th_fn(void *arg)
{
if (signal(SIGALRM, sig_handler) == SIG_ERR) { // 注册 SIGALRM 信号
perror("signal error");
}
alarm(2); // 定时两秒
for (int i = 1; i <= 100; i++) {
printf("0x%lx i: %d\n", pthread_self(), i);
sleep(1);
}
return (void *)0;
}
int main()
{
int err;
pthread_t th;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
// 以分离状态启动子线程,主控线程持续睡眠
if ((err = pthread_create(&th, &attr, th_fn, (void *)0)) != 0) {
perror("pthread create error");
}
while (1) {
printf("control thread(0x%lx) is running...\n", pthread_self());
sleep(6);
}
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12# 输出
control thread(0x104694580) is running...
0x16b9cb000 i: 1
0x16b9cb000 i: 2
pthread id in the sig_handler: 0x104694580 # 信号处理函数是主控线程调用的
time out
control thread(0x104694580) is running... # 主控线程由于 SIGALRM 信号而中断睡眠
0x16b9cb000 i: 3
0x16b9cb000 i: 4
pthread id in the sig_handler: 0x104694580
time out
...进程中的每个线程都有自己的信号屏蔽字和信号未决字。信号的处理方式是所有线程共享的。进程中的信号是递送到单个线程的。可以通过线程的信号屏蔽函数实现信号屏蔽。
1
2
3
int pthread_sigmask(int how, const sigset_t *restrict set,
sigset_t *restrict oset);
对上例中的主控线程屏蔽 SIGALRM 信号,从而使子线程捕获。
1
2
3
4
5
6
7 // 在 main 中加入
{
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGALRM);
pthread_sigmask(SIG_SETMASK, &set, NULL);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 # 输出
control thread(0x104fc8580) is running...
0x16b037000 i: 1
0x16b037000 i: 2
pthread id in the sig_handler: 0x16b037000 // SIGALRM 信号被子线程捕获
time out
0x16b037000 i: 3
0x16b037000 i: 4
pthread id in the sig_handler: 0x16b037000
time out
0x16b037000 i: 5
0x16b037000 i: 6
control thread(0x104fc8580) is running... // 主控线程睡醒
pthread id in the sig_handler: 0x16b037000
time out
0x16b037000 i: 7
0x16b037000 i: 8
pthread id in the sig_handler: 0x16b037000
time out