1. POSIX简介
POSIX表示可移植操作系统接口(Portable Operating System Interface of UNIX,缩写为POSIX),POSIX标准定义了操作系统为应用程序提供的接口标准。
这里介绍的是关于POSIX线程的内容,POSIX线程的API都在pthread.h文件中,如果已经安装了,可以通过以下命令查看所有API:
man -k pthread
查看某个API的用法:
man pthread_create
如果没有安装,可以通过以下命令去安装POSIX文档:
sudo apt-get install manpages-posix-dev
2. 创建线程与线程结束:
新建01.c文件:
1 #include<stdlib.h>
2 #include<stdio.h>
3 #include<unistd.h>
4 #include<pthread.h>
5
6 void* thr_fun(void* arg) {
7 char* no = (char*)arg;
8 int i = 0;
9 for(; i < 10; i++) {
10 printf("thread:%s,i:%d\n", no, i);
11 if(i == 5) {
12 //线程结束(自杀)
13 pthread_exit((void*)2);
14 }
15 }
16 return (void*)1;
17 }
18
19 void main() {
20 printf("man thread\n");
21 //线程的id
22 pthread_t tid;
23 //第二个参数表示属性,NULL表示默认属性
24 //thr_fun,线程创建之后执行的函数
25 pthread_create(&tid, NULL, thr_fun, "1");
26 void* retval;
27 //等待tid线程结束
28 //thr_fun与pthread_exit退出时的参数,都作为第二个参数的内容
29 pthread_join(tid, &retval);
30 printf("retval:%d\n", (int)retval);
31 }
上述线程自杀用pthread_exit()
,而线程他杀用pthread_cancel()
将01.c文件编译成可执行文件:
gcc 01.c -o 01 -lpthread
这时候在该目录下生成01文件,此时我们就可以执行该文件了:
./01
输出为:
man thread
thread:1,i:0
thread:1,i:1
thread:1,i:2
thread:1,i:3
thread:1,i:4
thread:1,i:5
retval:2
3. 互斥锁的使用
新建02.c文件:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<pthread.h>
5
6 int i = 0;
7 //互斥锁
8 pthread_mutex_t mutex;
9 void* thr_fun(void* arg) {
10 //加锁
11 pthread_mutex_lock(&mutex);
12 char* no = (char*)arg;
13 for(; i < 5; i++) {
14 printf("thread:%s,i:%d\n", no, i);
15 sleep(1);
16 }
17 i = 0;
18 //解锁
19 pthread_mutex_unlock(&mutex);
20 }
21
22 void main() {
23 pthread_t tid1, tid2;
24 //初始化互斥锁
25 pthread_mutex_init(&mutex, NULL);
26
27 pthread_create(&tid1, NULL, thr_fun, "NO1");
28 pthread_create(&tid2, NULL, thr_fun, "NO2");
29
30 pthread_join(tid1, NULL);
31 pthread_join(tid2, NULL);
32
33 //销毁互斥锁
34 pthread_mutex_destroy(&mutex);
35 }
上面的代码中:
- 我们通过pthread_mutex_init先初始化互斥锁,再通过创建NO1线程和NO2线程,通过pthread_join去阻塞主线程,直到子线程结束,最后销毁互斥锁
- 在某个线程执行的时候,首先通过pthread_mutex_lock加锁,当该线程执行完了之后,再通过pthread_mutex_unlock释放锁,让另外的一个线程去加锁。
- 互斥锁就是将一个线程做完,然后另一个线程再做。
编译02.c文件:
gcc 02.c -o 02 -lpthread
运行02文件:
thread:NO2,i:0
thread:NO2,i:1
thread:NO2,i:2
thread:NO2,i:3
thread:NO2,i:4
thread:NO1,i:0
thread:NO1,i:1
thread:NO1,i:2
thread:NO1,i:3
thread:NO1,i:4
可以发现,先将线程NO2执行完了,才会执行线程NO1,这就是互斥锁的作用所在。
我在shell5中查找pthread_create是可以查到用法的,但是我查找pthread_mutex_init中是无法查找的,也试过先更新sudo,再使用下面的命令下载也不行:
sudo apt-get install manpages-posix-dev
后来发现这是man不完整的原因,利用下列命令去下载完整版:
sudo apt-get update
安装标准C的帮助文档
sudo apt-get install libc-dev
sudo apt-get install glibc-doc
下载过程中遇到y/n,一律输入y进行下载。最后查看pthread_mutex_init就可以了
man pthread_mutex_init
4. 生产者、消费者模型
上面我们已经了解了互斥锁的用法了,接下来我们用互斥锁和条件变量来实现生产者、消费者模型。
其实视频解码的绘制使用就是生产者--消费者模型。图片的下载显示也是基于这种模型。比如说我们生产者生成的产品,放到一个队列里面,当生产者生产出产品的时候就会发送信号通知消费者去消费,例如RTMP推流的时候,我们本地采集音视频的时候就需要一种队列,因为本地的压缩比网络上传要快。
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<pthread.h>
5
6 //互斥锁
7 pthread_mutex_t mutex;
8 //条件变量
9 pthread_cond_t has_product;
10 //队列
11 int ready;
12
13 //生产着
14 void* thr_producer(void* arg) {
15 int no = (int)arg;
16 for(;;) {
17 //加锁
18 pthread_mutex_lock(&mutex);
19 //往队列中添加产品
20 ready++;
21 printf("producer %d produce\n", no);
22 //通知消费者,有新的产品可以消费了
23 pthread_cond_signal(&has_product);
24 //解锁
25 pthread_mutex_unlock(&mutex);
26 sleep(1);
27 }
28 }
29
30 void* thr_consumer(void* arg) {
33 pthread_mutex_lock(&mutex);
34 if(ready == 0) {
35 //没有产品,继续等待
36 pthread_cond_wait(&has_product, &mutex);
37 printf("consumer %d wait\n", no);
38 }
39 //有产品,消费产品
40 ready--;
41 printf("consumer %d consume\n", no);
42 sleep(1);
43 pthread_mutex_unlock(&mutex);
44 }
45 }
46
47 void main() {
48 //初始化互斥锁和条件变量
49 pthread_mutex_init(&mutex, NULL);
50 pthread_cond_init(&has_product, NULL);
51
52 pthread_t tid_p, tid_c;
53 //生产者线程
54 pthread_create(&tid_p, NULL, thr_producer, 1);
55 //消费者线程
56 pthread_create(&tid_c, NULL, thr_consumer, 2);
57
58 //等待
59 pthread_join(tid_p, NULL);
60 pthread_join(tid_c, NULL);
61
62 //销毁
63 pthread_mutex_destroy(&mutex);
64 pthread_cond_destroy(&has_product);
65 }
上面的代码中我们创建了两个线程,分别是生产者线程和消费者线程,生产者线程循环添加产品,添加完了之后通过消费者去消费,消费者发现有产品就消费产品,没有产品,就继续等待,两个线程都是利用互斥锁保证本身线程的执行。
但是这样单独一个生产者线程和消费者线程显然是不够的,实际使用中往往是多个生产者线程和消费者线程,接下来我将循环产生多个生产者线程和消费者线程:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<pthread.h>
5 #define PRODUCER_NUM 1
6 #define CONSUMER_NUM 2
7 pthread_t pids[PRODUCER_NUM + CONSUMER_NUM];
8 //互斥锁
9 pthread_mutex_t mutex;
10 //条件变量
11 pthread_cond_t has_product;
12
13 //产品队列
14 int ready = 0;
15
16 //生产者
17 void* thr_producer(void* arg) {
18 int no = (int)arg;
19 for(;;) {
20 //加锁
21 pthread_mutex_lock(&mutex);
22 //往队列中添加产品
23 ready++;
24 printf("producer %d produce\n", no);
25 //通知消费者,有新的产品可以消费了
26 pthread_cond_signal(&has_product);
27 //解锁
28 pthread_mutex_unlock(&mutex);
29 sleep(1);
30 }
31 }
32
33 //消费者
34 void* thr_consumer(void* arg) {
35 int no = (int)arg;
36 for(;;) {
37 //加锁
38 pthread_mutex_lock(&mutex);
39 while(ready == 0) {
40 printf("consumer %d wait\n", no);
41 //没有产品,继续等待
42 //1.阻塞 等待has_product被唤醒
43 //2.释放互斥锁,pthread_mutex_unlock
44 //3.被唤醒时,解除阻塞,重新申请获得互斥锁 pthread_mutex_lock
45 pthread_cond_wait(&has_product, &mutex);
46 }
47 //有产品,消费产品
48 ready--;
49 printf("consumer %d consume\n", no);
50 //解锁
51 pthread_mutex_unlock(&mutex);
52 sleep(3);
53 }
54 }
55
56 void main() {
57 //初始化互斥锁和条件变量
58 pthread_mutex_init(&mutex, NULL);
59 pthread_cond_init(&has_product, NULL);
60 int i = 0;
61 for(; i < PRODUCER_NUM; i++) {
62 //生产者线程
63 pthread_create(&pids[i], NULL, thr_producer, (void*)i);
64 }
65
66 i = 0;
67 for(; i < CONSUMER_NUM; i++) {
68 //消费者线程
69 pthread_create(&pids[PRODUCER_NUM + i], NULL, thr_consumer, (void*)i);
70 }
71
72 i = 0;
73 for(; i < PRODUCER_NUM + CONSUMER_NUM; i++) {
74 //等待
75 pthread_join(pids[i], NULL);
76 }
77
78 //销毁互斥锁和条件变量
79 pthread_mutex_destroy(&mutex);
80 pthread_cond_destroy(&has_product);
81 }
编译生成可执行文件:
gcc 03.c -o 03 -lpthread
执行文件:
./03
执行后终端截图如下:
可以按Ctrl + c停止。
喜欢本篇博客的简友们,就请来一波点赞,您的每一次关注,将成为我前进的动力,谢谢!