Linux系统学习——线程、互斥锁、条件篇
一、Linux线程和进程概念解析
典型的UNIX/Linux进程可以看成只有一个控制线程:一个进程在同一时刻只做一件事情。有了多个控制线程后,在程序设计时可以把进程设计成在同一时刻做不止一件事,每个线程各自处理独立的任务
(1)进程与线程
1. 进程是程序执行时的一个实例,是担当分配系统资源(CPU时间、内存等)的基本单位
2. 进程本身不是基本运行单位,而是线程的容器
3.一个进程里头可以有多个线程,不创建线程的情况下,至少有一个线程
4.进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,但是而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,进程的运行效率要差一些
(2)使用线程的理由(面试笔记)
1. 进程有独立的地址空间,线程没有单独的地址空间(同一进程内的线程共享进程的地址空间)
2. 使用多线程的理由之一是和进程相比,它是一种非常”节俭”的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种”昂贵”的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别
3. 使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方
可参考大佬解析:Linux多线程编程初探
二、线程API解析:
(1)线程的创建
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
// 返回:若成功返回0,否则返回错误编号
1. 当pthread_create成功返回时,由tidp指向的内存单元被设置为新创建线程的线程ID。
2. attr参数用于定制各种不同的线程属性,暂可以把它设置为NULL,以创建默认属性的线程。
3. 新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg。
4。 如果需要向 start_rtn函数 传递的参数不止一个,那么需要把这些参数放到一个结构体中,然后把这个结构体对象的地址作为arg参数传入
举例:
void *hendler(void *arg)
{
printf("t1: The pthread is %ld\n",(unsigned long)pthread_self()); //该 API 打印线程ID号,下面会解析
printf("t1: num is %d\n", *((int *)arg));
}
int ret;
pthread_t t1;
int num = 100;
ret = pthread_create(&t1,NULL,hendler,(void *)&num );
// 参数指针类型需要注意 取地址 &
(2)线程的退出
#include <pthread.h>
int pthread_exit(void *rval_ptr);
//rval_ptr是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以通过调用 pthread_join 函数访问到这个指针
单个线程可以通过以下三种方式退出,在不终止整个进程的情况下停止它的控制流:
(1)线程只是从启动例程中返回,返回值是线程的退出码。
(2)线程可以被同一进程中的其他线程取消。
(3)线程调用pthread_exit:
(3)线程的等待
#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);
// 返回:若成功返回0,否则返回错误编号
- 调用这个函数的线程将一直阻塞,直到指定的线程调用 pthread_exit、从启动例程中返回或者被取消。如果例程只是从它的启动例程返回 i , rval_ptr 将包含返回码。如果线程被取消,由 rval_ptr 指定的内存单元就置为 PTHREAD_CANCELED。
- 可以通过调用 pthread_join 自动把线程置于分离状态,这样资源就可以恢复。如果线程已经处于分离状态,pthread_join 调用就会失败,返回 EINVAL。
- 如果对线程的返回值不感兴趣,可以把 rval_ptr 置为NULL。在这种情况下,调用 pthread_join 函数将等待指定的线程终止,但并不获得线程的终止状态
(2)(3)demo2.c 演示:
整形变量退出:
1 #include<stdio.h>
2 #include<pthread.h>
3
4 void *hendler(void *arg)
5 {
6 static int p = 10;
7
8 printf("t1: The pthread is %ld\n",(unsigned long)pthread_self());
9 printf("t1: num is %d\n", *((int *)arg));
10 pthread_exit((void *)&p); //退出线程
11 }
12
13 int main()
14 {
15 int ret;
16 pthread_t t1;
17 int num = 100;
18
19 int *pret = NULL;
20
21 ret = pthread_create(&t1,NULL,hendler,(void *)&num); //创建线程
22
23 if(ret == 0) //创建线程成功
24 {
25 printf("create pthread successs!\n");
26 }
27
28 printf("main: The pthread is %ld\n",(unsigned long)pthread_self()); //打印线程ID号
29
30 pthread_join(t1,(void **)&pret ); //输出退出线程的 exit 里面的值之前,会阻塞在此
31
32 printf("t1 quit : %d\n",*pret); //输出退出线程的 exit 里面的值
33 return 0;
34 }
字符串变量退出(demo3.c):
1 #include<stdio.h>
2 #include<pthread.h>
3
4 void *hendler(void *arg)
5 {
6
7 static char *p = "t1 pthread";
8
9 printf("t1: The pthread is %ld\n",(unsigned long)pthread_self());
10 printf("t1: num is %d\n", *((int *)arg));
11 pthread_exit((void *)p);
12 }
13
14 int main()
15 {
16 int ret;
17 pthread_t t1;
18 int num = 100;
19
20 char *pret = NULL;
21
22 ret = pthread_create(&t1,NULL,hendler,(void *)&num);
23
24 if(ret == 0)
25 {
26 printf("create pthread successs!\n");
27 }
28
29 printf("main: The pthread is %ld\n",(unsigned long)pthread_self());
30
31 pthread_join(t1,(void **)&pret );
32
33 printf("t1 quit : %s\n",pret);
34 return 0;
35 }
(4)线程的ID获取及比较
#include <pthread.h>
pthread_t pthread_self(void);
// 返回:调用线程的ID
对于线程ID比较,为了可移植操作,我们不能简单地把线程ID当作整数来处理,因为不同系统对线程ID的定义可能不一样。我们应该要用下面的函数:
#include <pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2);
// 返回:若相等则返回非0值,否则返回0
三、互斥锁API解析:
互斥量本质为一把锁,加锁和解锁之间的代码都为互斥量
互斥变量用 pthread_mutex_t 数据类型表示。在使用互斥变量前必须对它进行初始化,可以把它置为常量 PTHREAD_MUTEX_INITIALIZER(只对静态分配的互斥量),也可以通过调用 pthread_mutex_init 函数进行初始化。如果动态地分配互斥量(例如通过调用malloc函数),那么在释放内存前需要调用pthread_mutex_destroy。
头文件:
#include<pthread.h>
1. 创建及销毁互斥锁
函数原型:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t mutex);
// 返回:若成功返回0,否则返回错误编号
- 互斥变量用 pthread_mutex_t 数据类型表示。在使用互斥变量前必须对它进行初始化
- 可以把它置为常量PTHREAD_MUTEX_INITIALIZER(只对静态分配的互斥量),也可以通过调用pthread_mutex_init函数进行初始化。
- 如果动态地分配互斥量(例如通过调用malloc函数),那么在释放内存前需要调用pthread_mutex_destroy
pthread_mutex_t mutex; //定义一把互斥锁变量,以mutex为例子
2.加锁和解锁
#include <pthread.h>
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,否则返回错误编号
- 如果线程不希望被阻塞,它可以使用 pthread_mutex_trylock 尝试对互斥量进行加锁。如果调用pthread_mutex_trylock 时互斥量处于未锁住状态,那么 pthread_mutex_trylock 将锁住互斥量,不会出现阻塞并返回 0,否则 pthread_mutex_trylock 就会失败,不能锁住互斥量,而返回 EBUSY。
样例demo5.c:
1 #include<stdio.h>
2 #include<pthread.h>
3
4
5 pthread_mutex_t mutex; //动态创建互斥锁
6
7 void *hendler(void *arg)
8 {
9 int i=0;
10 pthread_mutex_lock(&mutex); //加锁
11
12 for(i=0;i<5;i++) // t1线程先运行5次
13 {
14 printf("t1: The pthread is %ld\n",(unsigned long)pthread_self());
15 printf("t1: num is %d\n", *((int *)arg));
16 sleep(1);
17 }
18 pthread_mutex_unlock(&mutex); //解锁
19
20 }
21
22 void *hendler2(void *arg)
23 {
24 pthread_mutex_lock(&mutex);
25
26 printf("t2: The pthread is %ld\n",(unsigned long)pthread_self());
27 printf("t2: num is %d\n", *((int *)arg));
28
29 pthread_mutex_unlock(&mutex);
30
31 }
32
33 void *hendler3(void *arg)
34 {
35 pthread_mutex_lock(&mutex);
36
37 printf("t3: The pthread is %ld\n",(unsigned long)pthread_self());
38 printf("t3: num is %d\n", *((int *)arg));
39
40 pthread_mutex_unlock(&mutex);
41
42 }
43
44 int main()
45 {
46 int ret;
47 pthread_t t1;
48 pthread_t t2;
49 pthread_t t3;
50 int num = 100;
51
52 pthread_mutex_init(&mutex,NULL); //动态初始化互斥锁
53
54 ret = pthread_create(&t1,NULL,hendler,(void *)&num); //创建t1线程
55
56 if(ret == 0)
57 {
58 printf("main: 1 create pthread successs!\n");
59 }
60
61 ret = pthread_create(&t2,NULL,hendler2,(void *)&num); //创建t2线程
62
63 if(ret == 0)
64 {
65 printf("main: 2 create pthread successs!\n");
66 }
67 printf("main 2: The pthread is %ld\n",(unsigned long)pthread_self());
68
69
70 ret = pthread_create(&t3,NULL,hendler3,(void *)&num); //创建t3线程
71
72
73 pthread_join(t1,NULL); //等待t1线程解锁结束
74 pthread_join(t2,NULL); //等待t2线程解锁结束
75 pthread_join(t3,NULL); //等待t3线程解锁结束
76
77
78 return 0;
79 }
结果展示:
补充(造成死锁的情况):
在定义使用多个互斥锁的情况下,由于分别在两个线程中(或者以后工作多线程中),分别对不同的互斥锁进行加锁,有可能会导致别的线程找不到这把锁,从而导致双方因为拿了彼此都需要的锁,造成阻塞在拿锁的执行代码下,造成死锁!!
以demo7.c为样例:
1 #include<stdio.h>
2 #include<pthread.h>
3
4 pthread_mutex_t mutex; //创建互斥锁 mutex
5 pthread_mutex_t mutex2; //创建互斥锁 mutex2
6
7 void *hendler(void *arg)
8 {
9 int i=0;
10 pthread_mutex_lock(&mutex); //对mutex 进行加锁
11 sleep(1);
12 pthread_mutex_lock(&mutex2); //对mutex2 进行加锁
13
14 for(i=0;i<5;i++)
15 {
16 printf("t1: The pthread is %ld\n",(unsigned long)pthread_self());
17 printf("t1: num is %d\n", *((int *)arg));
18 sleep(1);
19 }
20 pthread_mutex_unlock(&mutex);
21
22 }
23
24 void *hendler2(void *arg)
25 {
26 pthread_mutex_lock(&mutex2); //对mutex2 进行加锁
27 sleep(1);
28 pthread_mutex_lock(&mutex); //对mutex 进行加锁
29
30 printf("t2: The pthread is %ld\n",(unsigned long)pthread_self());
31 printf("t2: num is %d\n", *((int *)arg));
32
33 pthread_mutex_unlock(&mutex);
34
35 }
36
37 void *hendler3(void *arg)
38 {
39 pthread_mutex_lock(&mutex);
40
41 printf("t3: The pthread is %ld\n",(unsigned long)pthread_self());
42 printf("t3: num is %d\n", *((int *)arg));
43
44 pthread_mutex_unlock(&mutex);
45
46 }
47
48 int main()
49 {
50 int ret;
51 pthread_t t1;
52 pthread_t t2;
53 pthread_t t3;
54 int num = 100;
55
56 pthread_mutex_init(&mutex,NULL);
57 pthread_mutex_init(&mutex2,NULL);
58
59 ret = pthread_create(&t1,NULL,hendler,(void *)&num);
60
61 if(ret == 0)
62 {
63 printf("main: 1 create pthread successs!\n");
64 }
65
66 ret = pthread_create(&t2,NULL,hendler2,(void *)&num);
67
68 if(ret == 0)
69 {
70 printf("main: 2 create pthread successs!\n");
71 }
72 printf("main 2: The pthread is %ld\n",(unsigned long)pthread_self());
73
74
75 ret = pthread_create(&t3,NULL,hendler3,(void *)&num);
76
77
78 pthread_join(t1,NULL);
79 pthread_join(t2,NULL);
80 pthread_join(t3,NULL);
81
82 pthread_mutex_destroy(&mutex); //销毁互斥锁,释放内存
83 pthread_mutex_destroy(&mutex2); //销毁互斥锁,释放内存
84
85
86 return 0;
87 }
运行结果:
由于在 t1 线程中,先是给 mutex 互斥锁加上了锁,此时由于给 mutex 的锁已经给 t1线程 拿走了 ,所以 线程 t2 里面给 mutex 加锁的操作就会阻塞卡死,同理在 t2线程 中给 mutex2 加锁,又会导致 t1线程 中给 mutex2 加锁的操作因为 t2线程 拿走了 mutex2的锁 ,从而 t1线程 中给 mutex2 加锁的操作会阻塞卡死。这样两边互相抢走了锁,导致造成了死锁的情况,运行的光标就会卡死
四、条件变量API解析:
- 条件变量是线程另一可用的同步机制。条件变量给多个线程提供了一个会合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生
- 条件本身是由互斥量保护的。线程在改变条件状态前必须首先锁住互斥量,其他线程在获得互斥量之前不会察觉到这种改变,因为必须锁定互斥量以后才能计算条件
- 条件变量使用之前必须首先初始化,pthread_cond_t 数据类型代表的条件变量可以用两种方式进行初始化,可以把常量 PTHREAD_COND_INITIALIZER 赋给静态分配的条件变量,但是如果条件变量是动态分配的,可以使用 pthread_cond_destroy 函数对条件变量进行去除初始化
(1)创建及销毁条件变量:
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t cond);
// 返回:若成功返回0,否则返回错误编号
演示:
pthread_cond_t cond; // 创建条件变量
pthread_cond_init(&cond,NULL); // 初始化条件变量
pthread_cond_destroy(&cond); // 销毁条件变量
(2)条件变量的等待:
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex,cond struct timespec *restrict timeout);
// 返回:若成功返回0,否则返回错误编号
pthread_cond_wait(&cond,&mutex);
- pthread_cond_wait 等待条件变为真。如果在给定的时间内条件不能满足,那么会生成一个代表一个出错码的返回变量。传递给 pthread_cond_wait 的互斥量对条件进行保护,调用者把锁住的互斥量传给函数。函数把调用线程放到等待条件的线程列表上,然后对互斥量解锁,这两个操作都是原子操作。这样就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道,这样线程就不会错过条件的任何变化。pthread_cond_wait 返回时,互斥量再次被锁住。
- pthread_cond_timedwait函数的工作方式与 pthread_cond_wait 函数类似,只是多了一个 timeout 。timeout 指定了等待的时间,它通过 timespec 这个结构体指定
(3)触发
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t cond); // 发送一条信号
int pthread_cond_broadcast(pthread_cond_t cond); // 发送多条信号
// 返回:若成功返回0,否则返回错误编号
注意:
- 这两个函数可以用于通知线程条件已经满足。pthread_cond_signal 函数将唤醒等待该条件的某个线程,而 pthread_cond_broadcast 函数将唤醒等待该条件的所有进程。
- 注意一定要在改变条件状态以后再给线程发信号
(2)(3)这里以demo8.c为样例:
1 #include<stdio.h>
2 #include<pthread.h>
3 #include <unistd.h>
4
5 int cnt = 0;
6
7 pthread_mutex_t mutex; // 动态化创建互斥锁
8 pthread_cond_t cond; // 动态化创建条件变量
9
10 void *hendler(void *arg)
11 {
12 printf("t1: The pthread is %ld\n",(unsigned long)pthread_self());
13 printf("t1: num is %d\n", *((int *)arg));
14
15 static int sum=0;
16
17 while(1)
18 {
19 pthread_cond_wait(&cond,&mutex); //等待t2线程中发送信号过来之前,阻塞在此
20 if(cnt == 3)
21 {
22 printf("t1 always quit =========================\n");
23 }
24 printf("t1 : %d\n",cnt);
25 sleep(1);
26 cnt = 0;
27 if(sum++ ==5) //执行5次 退出该进程
28 {
29 exit(1);
30 }
31 }
32 }
33
34 void *hendler2(void *arg)
35 {
36 printf("t2: The pthread is %ld\n",(unsigned long)pthread_self());
37 printf("t2: num is %d\n", *((int *)arg));
38
39 while(1)
40 {
41
42 printf("t2 : %d\n",cnt);
43 pthread_mutex_lock(&mutex);
44
45 cnt++;
46
47 pthread_mutex_unlock(&mutex);
48 if(cnt == 3)
49 {
50 pthread_cond_signal(&cond); //当cnt达到 3 时,会触发条件变量给t1,t1将打印阻塞下的代码结果
51 }
52 sleep(1);
53 }
54 }
55
56 int main()
57 {
58 int ret;
59 pthread_t t1;
60 pthread_t t2;
61 int num = 100;
62
63 pthread_mutex_init(&mutex,NULL); //初始化互斥锁
64
65 pthread_cond_init(&cond,NULL); //初始化条件变量
66
67 ret = pthread_create(&t1,NULL,hendler,(void *)&num);
68
69 if(ret == 0)
70 {
71 // printf("1 create pthread successs!\n");
72 }
73
74 ret = pthread_create(&t2,NULL,hendler2,(void *)&num);
75
76 if(ret == 0)
77 {
78 // printf("2 create pthread successs!\n");
79 }
80 // printf("main2: The pthread is %ld\n",(unsigned long)pthread_self());
81
82
83 pthread_join(t1,NULL);
84 pthread_join(t2,NULL);
85
86 pthread_mutex_destroy(&mutex); // 销毁互斥锁
87 pthread_cond_destroy(&cond); // 销毁条件变量
88
89 return 0;
90 }
结果:
当 t2 线程中的 cnt 累加到 3 的时候,触发pthread_cond_signal(&cond) 唤醒 t1 线程中等待该条件的 pthread_cond_wait(&cond,&mutex) ,所以会执行 wait 以下的代码,打印以上结果。
五、互斥锁和条件的动态、静态创建:
(1)动态下:
pthread_mutex_t mutex; //动态创建互斥锁
pthread_cond_t cond; //动态创建条件变量
//主函数里头:
pthread_mutex_init(&mutex);
pthread_cond_init(&cond);
(2) 静态下需要使用宏:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 使用宏静态创建互斥锁,不用在主函数main初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 使用宏静态创建条件变量,不用在主函数main初始化
记录学习生活,有部分函数没有用到,工作有需求时再使用查看
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/68483.html