LV05-03-线程同步-05-条件变量

本文主要是线程同步——条件变量的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows windows11
Ubuntu Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
SecureCRT Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日
开发板 正点原子 i.MX6ULL Linux阿尔法开发板
uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03)
linux内核 linux-4.15(NXP官方提供)
STM32开发板 正点原子战舰V3(STM32F103ZET6)
点击查看本文参考资料
参考方向 参考原文
------
点击查看相关文件下载
--- ---

一、条件变量

条件变量是线程可用的另一种同步机制。条件变量用于自动阻塞线程,知道某个特定事件发生或某个条件满足为止,通常情况下,条件变量是和互斥锁一起搭配使用的,这是因为条件的检测是在互斥锁的保护下进行的,也就是说条件本身是由互斥锁保护的,线程在改变条件状态之前必须先锁住互斥锁,不然就可能引发线程不安全的问题。

条件变量就相当于生产者和消费者模的式,生产者这边负责生产产品、而消费者负责消费产品,对于消费者来说,没有产品的时候只能等待产品出来,有产品就使用它。

二、基本操作

1. 条件变量初始化

1.1 pthread_cond_init() 

1.1.1 函数说明

linux下可以使用man pthread_cond_init命令查看该函数的帮助手册。

1
2
3
4
5
6
/* Compile and link with -pthread. */
#include <pthread.h>
/* man 手册中的声明 */
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
/* 一些资料中的声明 */
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);

额……,😥跟上边一样,使用常见的声明格式把,毕竟参数都是一样的,但是常见的还是更容易理解一些。

【函数说明】该函数是以动态方式初始化一个条件变量。

【函数参数】

  • condpthread_cond_t *类型,指向需要进行初始化操作的条件变量对象
  • attrconst pthread_condattr_t *类型,指向一个pthread_condattr_t类型对象,该对象用于描述条件变量的属性,若将参数attr设置为NULL,则表示条件变量的属性设置为默认值,在这种情况下其实就等价于PTHREAD_COND_INITIALIZER这种方式初始化,而不同之处在于,使用宏不进行错误检查。

【返回值】int类型,成功返回0;失败将返回一个非0的错误码。

【使用格式】一般情况下基本使用格式如下:

1
2
3
4
5
6
7
8
9
/* 需要包含的头文件 */
#include <pthread.h>

/* 至少应该有的语句 */
pthread_cond_t cond;/* 定义全局变量 */
pthread_cond_init(&cond, NULL);/* 初始化条件变量 */
/* 或者 */
pthread_cond_t *cond = malloc(sizeof(pthread_cond_t)); /* 定义为全局指针变量 */
pthread_cond_init(cond, NULL);/* 初始化条件变量 */

【注意事项】

(1)在使用条件变量之前必须对条件变量进行初始化操作,使用PTHREAD_COND_INITIALIZER宏或者函数pthread_cond_init()都行;

(2) 对已经初始化的条件变量再次进行初始化,将可能会导致未定义行为。

1.1.2 使用实例

暂无。

1.2 PTHREAD_COND_INITIALIZER 

1.2.1 函数说明

linux下可以使用man 3 pthread_cond_init命令查看该宏的定义格式。

1
2
3
/* Compile and link with -pthread. */
#include <pthread.h>
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; /* cond为条件变量名称,符合变量标识符定义规则即可。*/

【函数说明】该宏用于使用默认属性初始化一个条件变量。

【定义原型】该宏定义在pthread.h文件中,可以在终端中使用以下命令查看pthread.h文件位置:

1
locate pthread.h # 若出现locate 命令未安装之类的,按照提示安装即可

定义形式如下:

1
2
/* Conditional variable handling.  */
#define PTHREAD_COND_INITIALIZER { { 0, 0, 0, 0, 0, (void *) 0, 0, 0 } }

【返回值】none

【使用格式】一般情况下基本使用格式如下:

1
2
3
4
5
/* 需要包含的头文件 */
#include <pthread.h>

/* 至少应该有的语句 */
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

【注意事项】

(1)PTHREAD_COND_INITIALIZER宏已经携带了条件变量默认属性。

(2)只适用于在定义的时候就直接进行初始化,对于其它情况则不能使用这种方式。

1.2.2 使用实例

暂无。

2. 通知和等待

2.1 pthread_cond_signal() 

2.1.1 函数说明

linux下可以使用man 3 pthread_cond_signal命令查看该宏的定义格式。

1
2
3
/* Compile and link with -pthread. */
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);

【函数说明】函数向条件变量发送信号(至少唤醒一个线程)。

【函数参数】

  • condpthread_cond_t *类型,指向已经初始化过的条件变量对象

【返回值】int类型,成功返回0;失败将返回一个非0的错误码。

【使用格式】一般情况下基本使用格式如下:

1
2
3
4
5
6
7
8
/* 需要包含的头文件 */
#include <pthread.h>

/* 至少应该有的语句 */
pthread_cond_t cond;/* 定义全局变量 */
pthread_cond_init(&cond, NULL);/* 初始化条件变量 */

pthread_cond_signal(&cond);/* 向条件变量发送信号 */

【注意事项】对阻塞于pthread_cond_wait()的多个线程,pthread_cond_signal()函数至少能唤醒一个线程。

2.1.2 使用实例

暂无。

2.2 pthread_cond_broadcast() 

2.2.1 函数说明

linux下可以使用man pthread_cond_broadcast命令查看该函数的帮助手册。

1
2
3
/* Compile and link with -pthread. */
#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cond);

【函数说明】该函数向条件变量发送信号(唤醒所有线程,这叫线程的惊群效应)。

【函数参数】

  • condpthread_cond_t *类型,指向已经初始化过的条件变量对象

【返回值】int类型,成功返回0;失败将返回一个非0的错误码。

【使用格式】一般情况下基本使用格式如下:

1
2
3
4
5
6
7
8
/* 需要包含的头文件 */
#include <pthread.h>

/* 至少应该有的语句 */
pthread_cond_t cond;/* 定义全局变量 */
pthread_cond_init(&cond, NULL);/* 初始化条件变量 */

pthread_cond_broadcast(&cond);/* 向条件变量发送信号 */

【注意事项】

(1)对阻塞于pthread_cond_wait()的多个线程,pthread_cond_broadcast()函数能唤醒所有线程。

(2)使用pthread_cond_broadcast()函数总能产生正确的结果,唤醒所有等待状态的线程,但函数pthread_cond_signal()会更为高效,因为它只需确保至少唤醒一个线程即可。

(3)当调用pthread_cond_broadcast()同时唤醒所有线程时,由于互斥锁也只能被某一线程锁住,其它线程获取锁失败又会陷入阻塞。

2.2.2 使用实例

暂无。

2.3 pthread_cond_wait() 

2.3.1 函数说明

linux下可以使用man pthread_cond_wait命令查看该函数的帮助手册。

1
2
3
4
5
6
/* Compile and link with -pthread. */
#include <pthread.h>
/* man 手册中的声明 */
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
/* 一些资料中的声明 */
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

【函数说明】程序中使用条件变量,当判断某个条件不满足时,调用此函数将线程设置为等待状态(阻塞)。

【函数参数】

  • condpthread_cond_t *类型,指向需要进行初始化操作的条件变量对象
  • mutexpthread_mutex_t *类型,指向一个互斥锁对象;前边说过的,条件变量通常是和互斥锁一起使用,因为条件的检测(条件检测通常是需要访问共享资源的)是在互斥锁的保护下进行的,也就是说条件本身是由互斥锁保护的。
点击查看函数内部对互斥锁的使用规则

pthread_cond_wait()函数内部会对参数mutex所指定的互斥锁进行操作,通常情况下,条件判断以及pthread_cond_wait()函数调用均在互斥锁的保护下,也就是说,在此之前线程已经对互斥锁加锁了。

调用pthread_cond_wait()函数时,调用者把互斥锁传递给函数,函数会自动把调用线程放到等待条件的线程列表上,然后将互斥锁解锁;当pthread_cond_wait()被唤醒返回时,会再次锁住互斥锁。

【返回值】int类型,成功返回0;失败将返回一个非0的错误码。

【使用格式】一般情况下基本使用格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 需要包含的头文件 */
#include <pthread.h>

/* 至少应该有的语句 */
pthread_cond_t cond;/* 定义全局变量 */
pthread_mutex_t mutex;/* 定义全局变量 */
pthread_cond_init(&cond, NULL);/* 初始化条件变量 */
pthread_mutex_init(&mutex, NULL);/* 初始化互斥锁 */

pthread_mutex_lock(&mutex);
/* 等待信号的条件 */
while(<表达式>)
{
pthread_cond_wait(&cond, &mutex);
}
/* 中间为共享资源的访问操作 */
pthread_mutex_unlock(&mutex);

【注意事项】条件变量并不保存状态信息,只是传递应用程序状态信息的一种通讯机制。如果调用pthread_cond_signal()pthread_cond_broadcast()向指定条件变量发送信号时,若无任何线程等待该条件变量,这个信号也就会自动消失。

2.3.2 使用实例

暂无。

2.4 pthread_cond_timedwait() 

2.4.1 函数说明

linux下可以使用man pthread_cond_timedwait命令查看该函数的帮助手册。

1
2
3
4
5
6
/* Compile and link with -pthread. */
#include <pthread.h>
/* man 手册中的声明 */
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);

还是这样一个函数,我们直接忽略restrict的存在好吧😆,反正都一样。

【函数说明】程序中使用条件变量,当判断某个条件不满足时,调用此函数将线程设置为等待状态(阻塞),等待条件变量的同时可以设置等待超时,超时就会退出,不会再继续等待。

【函数参数】

  • condpthread_cond_t *类型,指向已经初始化过的条件变量对象
  • mutexpthread_mutex_t *类型,指向一个互斥锁对象。
  • abstimestruct timespec *类型,表示等待的时间,它是一个绝对值,也就是距离1970-1-1 日的时间值,而不是一个时间段。比如说当前时间为2022-5-1 12:00:00.000,我们想通过这个函数设置最大超时为2500ms,那么就需要设置abstime时间为2022-5-1 12:00:02.500

【返回值】int类型,成功返回0;失败将返回一个非0的错误码。

【使用格式】none

【注意事项】此函数时间设定个人感觉还是很复杂的,暂时还木有用到过,后边用到了再补充。

2.4.2 使用实例

暂无。

3. 判断条件

使用条件变量,都会有与之相关的判断条件,通常情况下,会涉及到一个或多个共享变量。条件的判断必须使用while循环,而不是if语句,这是一种通用的设计原则:当线程从pthread_cond_wait()返回时,并不能确定判断条件的状态,应该立即重新检查判断条件,如果条件不满足,那就继续休眠等待。

pthread_cond_wait()返回后,并不能确定判断条件是真还是假,其理由如下:

(1)当有多于一个线程在等待条件变量时,任何线程都有可能会率先醒来获取互斥锁,率先醒来获取到互斥锁的线程可能会对共享变量进行修改,进而改变判断条件的状态。

(2)可能会发出虚假的通知。

4. 销毁条件变量

定义了条件变量之后,当不再需要条件变量时,应该将其销毁。

4.1 pthread_cond_destroy() 

4.1.1 函数说明

linux下可以使用man pthread_cond_destroy命令查看该函数的帮助手册。

1
2
3
/* Compile and link with -pthread. */
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);

【函数说明】该函数用于销毁一个不再使用的条件变量(条件变量对象实际上变成了未初始化的对象)。

【函数参数】

  • condpthread_cond_t *类型,指向已经初始化过的条件变量对象。

【返回值】int类型,用成功时返回0;失败将返回一个非0值的错误码。

【使用格式】一般情况下基本使用格式如下:

1
2
3
4
5
/* 需要包含的头文件 */
#include <pthread.h>

/* 至少应该有的语句 */
pthread_cond_destroy(&cond);/* 具体看定义的方式,是变量还是指针变量,指针变量则不需要&符号。*/

【注意事项】

(1)对没有进行初始化的条件变量进行销毁,也将可能会导致未定义行为。

(2)对某个条件变量而言,仅当没有任何线程等待它时,将其销毁才是最安全的。

(3)经pthread_cond_destroy()销毁的条件变量,可以再次调用pthread_cond_init()对其进行重新初始化。

(4)高版本的Ubuntu中需要确保条件变量没有线程在使用的情况下才能销毁,否则会导致程序卡死,至少我使用的Ubuntu21.04(64位)就是这样的。

4.1.2 使用实例

暂无。

三、条件变量的属性

条件变量与前边的各种锁一样,都可以设置属性,调用pthread_cond_init()函数初始化条件变量时,可以设置条件变量的属性,通过参数attr指定。条件变量包括两个属性:进程共享属性时钟属性。由于还没有用到过,暂时先提一下,后边用到了再补充笔记。

四、使用例程

例程这里,我们模拟消费者与生产者关系。

1. 使用实例1

本实例是一个生产者,一个消费者,也就是说,这个例程有一个消费者线程,一个生产者线程,通过条件变量来实现同步。

点击查看实例
test.c
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
#include <stdio.h>
#include <pthread.h>/* pthread_create pthread_exit pthread_cancel pthread_self */
#include <unistd.h> /* sleep */
#include <string.h> /* strerror */
#include <stdlib.h> /* malloc */

#define macro_judge 1 /* macro为1时进行Head是否为空的判断,测试消费者线程会怎样的情况,为1时,不会有信号丢失,为0时,会有信号丢失 */

struct product
{
int num; /* 产品编号 */
struct product * next;/* 下一个产品 */
};
struct product * Head = NULL;/* 定义一个产品线的头 */

/* 静态方式初始化互斥锁 */
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
/* 初始化条件变量 */
pthread_cond_t hasProduct = PTHREAD_COND_INITIALIZER;

/* 相关函数实现 */
void *producer(void *arg);
void *consumer(void *arg);

/* 主函数 */
int main(int argc, char *argv[])
{
int ret;
pthread_t tid1, tid2;
/* 1. 创建线程 */
ret = pthread_create(&tid1, NULL, producer, NULL);
printf("Create producer thread ,ret=%d,tid1=%lu\n", ret, tid1);
sleep(5);/* 先生产几件产品,然后再开启消费者线程 */
ret = pthread_create(&tid2, NULL, consumer, (void *)1);
printf("Create consumer thread ,ret=%d,tid2=%lu\n", ret, tid2);
sleep(1);

while(1)
{
sleep(1);
}
return 0;
}

/* 生产者线程 */
void *producer(void *arg)
{
/* 定义一个产品的结构体指针变量 */
struct product * pd;
int i = 0;
/* 设置线程分离,结束后自动回收 */
pthread_detach(pthread_self());
printf("This is a producer thread test!\n");

/* 生产产品 */
while(1)
{
/* 申请内存 */
pd = (struct product *)malloc(sizeof(struct product));
/* 判断内存是否申请成功 */
if(pd == NULL)
{
printf("product malloc failed!\n");
pthread_exit("product thread malloc failed!");
}
pd->num = i++;
printf("product %d is producing!\n", pd->num);
pthread_mutex_lock(&lock); /* 获取互斥锁 */
pd->next = Head;
Head = pd;
/* 发送产品生产完成信号 */
pthread_cond_signal(&hasProduct);
pthread_mutex_unlock(&lock);/* 解除互斥锁 */
sleep(1);
}
/* 退出线程 */
pthread_exit("producer thread return!");
}

/* 消费者线程 */
void *consumer(void *arg)
{
/* 设置线程分离,结束后自动回收 */
pthread_detach(pthread_self());
printf("This is a consumer thread test!\n");
/* 定义一个产品的结构体指针变量 */
struct product * pd;
while(1)
{
pthread_mutex_lock(&lock);/* 获取互斥锁 */
/* 等待产品生产完成信号 */
#if macro_judge == 1
while(Head == NULL)
#endif
{
/* 没有资源的时候进行等待,等待的时候会解除互斥锁,然后进入休眠,信号到了再重新获取互斥锁 */
pthread_cond_wait(&hasProduct,&lock);
}
pd = Head;
Head = pd->next;
printf("%d,Take product %d\n", (int)arg, pd->num);
free(pd);/* 释放内存 */
pthread_mutex_unlock(&lock);/* 解除互斥锁 */
}
pthread_exit("consumer thread return!");
}

程序中的宏macro_judge用于决定在消费者线程中是否添加产品余量的while循环:

  • macro_judge0,则不进行判断,直接进行等信号到来,但是这样,消费者线程开启之前的信号就全部丢失了。
  • macro_judge1,则加入判断,这样即便是消费者线程开启之前到来的信号也会被处理掉。

在终端执行以下命令编译程序:

1
2
gcc test.c -Wall -l pthread # 生成可执行文件 a.out 有一个警告,是线程创建传参造成的,正常现象
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Create producer thread ,ret=0,tid1=139928004916800
This is a producer thread test!
product 0 is producing!
product 1 is producing!
product 2 is producing!
product 3 is producing!
product 4 is producing!
Create consumer thread ,ret=0,tid2=139927996524096
This is a consumer thread test!
product 5 is producing!
1,Take product 5
product 6 is producing!
1,Take product 6
# 后边的省略 ......

根据结果发现,消费者线程启动前发出的商品信号丢失了,消费者线程并未获取到。

在终端执行以下命令编译程序:

1
2
gcc test.c -Wall -l pthread # 生成可执行文件 a.out  有一个警告,是线程创建传参造成的,正常现象
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Create producer thread ,ret=0,tid1=140716674655808
This is a producer thread test!
product 0 is producing!
product 1 is producing!
product 2 is producing!
product 3 is producing!
product 4 is producing!
Create consumer thread ,ret=0,tid2=140716666263104
This is a consumer thread test!
1,Take product 4
1,Take product 3
1,Take product 2
1,Take product 1
1,Take product 0
product 5 is producing!
1,Take product 5
product 6 is producing!
1,Take product 6
product 7 is producing!
1,Take product 7
# 后边的省略 ......

经打印结果发现,所有的信号都被消费者线程获取到了。

2. 使用实例2

本实例是一个生产者,三个消费者,也就是说,这个例程有三个消费者线程,一个生产者线程,通过条件变量来实现同步。

点击查看实例
test.c
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
110
111
112
113
114
115
#include <stdio.h>
#include <pthread.h>/* pthread_create pthread_exit pthread_cancel pthread_self */
#include <unistd.h> /* sleep */
#include <string.h> /* strerror */
#include <stdlib.h> /* malloc */

struct product
{
int num; /* 产品编号 */
struct product * next;/* 下一个产品 */
};
struct product * Head = NULL;/* 定义一个产品线的头 */

#define macro_judge 0 /* 0,不进行Head == NULL的判断;1,Head == NULL */
#define broadcast 1 /* 0,信号只能唤醒一个线程;1,信号将唤醒所有线程 */

/* 静态方式初始化互斥锁 */
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
/* 初始化条件变量 */
pthread_cond_t hasProduct = PTHREAD_COND_INITIALIZER;

/* 相关函数实现 */
int testConditionVariable(void);
void *producer(void *arg);
void *consumer(void *arg);

/* 主函数 */
int main(int argc, char *argv[])
{
int ret;
pthread_t tid1, tid2, tid3, tid4;
/* 1. 创建线程 */
ret = pthread_create(&tid1, NULL, producer, NULL);
printf("Create producer thread ,ret=%d,tid1=%lu\n", ret, tid1);
ret = pthread_create(&tid2, NULL, consumer, (void *)1);
printf("Create consumer thread ,ret=%d,tid2=%lu\n", ret, tid2);
ret = pthread_create(&tid3, NULL, consumer, (void *)2);
printf("Create consumer thread ,ret=%d,tid3=%lu\n", ret, tid3);
ret = pthread_create(&tid4, NULL, consumer, (void *)3);
printf("Create consumer thread ,ret=%d,tid4=%lu\n", ret, tid4);
sleep(1);

while(1)
{
sleep(1);
}
return 0;
}

/* 生产者线程 */
void *producer(void *arg)
{
/* 定义一个产品的结构体指针变量 */
struct product * pd;
int i = 0;
/* 设置线程分离,结束后自动回收 */
pthread_detach(pthread_self());
printf("This is a producer thread test!\n");

/* 生产产品 */
while(1)
{
/* 申请内存 */
pd = (struct product *)malloc(sizeof(struct product));
/* 判断内存是否申请成功 */
if(pd == NULL)
{
printf("product malloc failed!\n");
pthread_exit("product thread malloc failed!");
}
pd->num = i++;
printf("product %d is producing!\n", pd->num);
pthread_mutex_lock(&lock); /* 获取互斥锁 */
pd->next = Head;
Head = pd;
/* 发送产品生产完成信号 */
#if broadcast == 1
pthread_cond_broadcast(&hasProduct);/* 注意此函数发送的信号会被所有等待信号的线程得到 */
#else
pthread_cond_signal(&hasProduct);/* 注意此函数发送的信号只会被一个线程得到 */
#endif
pthread_mutex_unlock(&lock);/* 解除互斥锁 */
sleep(1);
}
/* 退出线程 */
pthread_exit("producer thread return!");
}

/* 消费者线程 */
void *consumer(void *arg)
{
/* 设置线程分离,结束后自动回收 */
pthread_detach(pthread_self());
printf("This is a consumer thread test!\n");
/* 定义一个产品的结构体指针变量 */
struct product * pd;
while(1)
{
pthread_mutex_lock(&lock);/* 获取互斥锁 */
/* 等待产品生产完成信号 */
#if macro_judge == 1
while(Head == NULL)
#endif
{
/* 没有资源的时候进行等待,等待的时候会解除互斥锁,然后进入休眠,信号到了再重新获取互斥锁 */
pthread_cond_wait(&hasProduct,&lock);
}
pd = Head;
Head = pd->next;
printf("%d,Take product %d\n", (int)arg, pd->num);
free(pd);/* 释放内存 */
pthread_mutex_unlock(&lock);/* 解除互斥锁 */
}
pthread_exit("consumer thread return!");
}

这里有两个宏,一共有四种情况测试,这里就不再放自己的测试情况了,只分析一下两个宏定义的作用:

程序中的宏macro_judge用于决定在消费者线程中是否添加产品余量的while循环:

macro_judge为0不进行Head == NULL的判断,直接进行等信号到来,但是这样,消费者线程开启之前的信号就全部丢失了。
macro_judge为1进行Head == NULL的判断,这样即便是消费者线程开启之前到来的信号也会被处理掉。

程序中的宏broadcast用于决定生产者线程中发出信号后,唤醒多少线程:

broadcast为0则将信号广播给1个线程,只会有1个线程会被唤醒。
broadcast为1则将信号广播给所有线程,所有线程都会被唤醒开始抢夺信号。

【注意事项】broadcast = 1时,若macro_judge = 0,则会发生段错误,若macro_judge = 1时正常。 发生原因:例如生产2件产品时,信号广播给所有线程,三个线程都会抢这两个信号,总有一个抢不到,若不进行判断,访问一个空指针,自然会出现内存访问错误 从而报段错误。