线程同步
1. 同步概念
所谓同步,即同时起步,协调一致。不同的对象,对“同步”的概念有所区别。在编程中所说的同步与生活中大家印象中的同步概念略有差别,“同”字应是协同、协助、互相配合。主旨在协同步调,按预定的先后次序执行。
1.1 线程同步
线程同步: 指一个线程发出某一功能调用时,在没有得到结果前给,该调用不返回。同时其它线程为保证数据的一致性,不能调用该功能。
例如:在100字节的共享内存中,线程a想全部填入1, 线程b想全部填如0,如果a在执行了50字节的操作后失去了cpu,此时线程b往共享内存中填入0,将会覆盖前面填入的1,当线程a再次获得cpu后将从失去cpu时刻的位置继续写入1,当运行结束,内存中100字节,既不全是1,也不全是0.
这种情况叫“与时间有关的错误”。为了避免这种数据混乱,线程需要同步。
1.2 数据混乱的原因
- 共享资源
- 调度随机
- 线程间缺乏必要的同步机制
在以上3点中,前2点不能改变,想要提高效率,传递数据,资源必须共享。但是要资源共享就会出现竞争,这样就会产生数据混乱,所以着重于第3点,时多个线程在访问资源时出现互斥。就可以避免数据混乱现象。
2. 互斥量 mutex
Linux中提供一把互斥锁 mutex
, 也叫互斥量
每个线程在对资源进行操作前应先加锁,成功加锁后才可以对资源进行操作,操作解释后再解锁。
在这过程中,资源还是共享的,线程间还是相互竞争的。但通过锁就将资源的访问变成互斥操作。
同一时刻,只允许一个线程持有该锁
当线程a对全局变量进行访问,线程b在访问前尝试去拿锁,但此时锁被线程a持有,线程b拿不到锁,就会阻塞,而此时线程c直接访问全局变量,依然可以访问,但是会出现数据混乱。
所以,互斥锁实质上是操作系统提供的“建议锁”,又称协同锁,建议在多线程访问共享资源是使用该机制。
2.1 主要应用函数
pthread_mutex_t
类型,实质上是一个结构体。为简化理解,应用时可以忽略其细节,简单的当成整数看待。
pthread_mutex_t mutex
变量mutex只有1、0两种取值。
2.1.1 pthread_mutex_init 函数
函数作用:初始化一个互斥锁(mutex初值可看作 1)
函数原型:
#include <pthread.h>
//restrict 关键字,作用:用于修饰指针,该关键字修饰的指针所指向的内存空间,只能由该指针操作,其余指向该空间的指针均无效
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
函数参数:
mutex:互斥锁
attr:锁属性,通常传NULL
返回值:
成功:0
失败:错误号
2.1.2 pthread_mutex_destroy 函数
函数作用:销毁一个互斥锁
函数原型:
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
函数参数:
mutex:互斥锁
返回值:
成功:0
失败:错误号
2.1.3 pthread_mutex_lock 函数
函数作用:加锁(可看作 mutex-- 操作),若加锁不成功,线程阻塞,直到有unlock函数唤醒为止
函数原型:
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
函数参数:
mutex:互斥锁
返回值:
成功:0
失败:错误号
2.1.4 pthread_mutex_unlock 函数
函数作用:解锁(可看作 mutex++ 操作), 同时将阻塞在lock处的线程全部唤醒,至于哪个线程先被唤醒,取决于优先级、调度。默认:先阻塞、先唤醒
函数原型:
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
函数参数:
mutex:互斥锁
返回值:
成功:0
失败:错误号
2.1.5 pthread_mutex_trylock 函数
函数作用:尝试加锁(非阻塞)
函数原型:
#include <pthread.h>
int pthread_mutex_trylock(pthread_mutex_t *mutex);
函数参数:
mutex:互斥锁
返回值:
成功:0
失败:错误号
2.2 简单示例
示例:不加互斥锁,让主线程和子线程轮流打印hello world
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <error.h>
void* call_back(void* arg) {
while(1) {
printf("hello ");
sleep(rand() % 3);
printf("world\n");
sleep(rand() % 3);
}
return NULL;
}
int main() {
pthread_t tid;
int ret;
ret = pthread_create(&tid, NULL, call_back, NULL);
if(ret != 0) {
fprintf(stderr, "pthread_create error:%s\n", strerror(ret));
exit(1);
}
while(1) {
printf("HELLO ");
sleep(rand() % 3);
printf("WORLD\n");
sleep(rand() % 3);
}
return 0;
}
/*结果,数据混乱
HELLO hello WORLD
HELLO world
WORLD
hello HELLO WORLD
HELLO world
WORLD
*/
示例:对上述示例改进
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <error.h>
pthread_mutex_t mutex; //定义锁
void* call_back(void* arg) {
int ret;
while(1) {
ret = pthread_mutex_lock(&mutex); //加锁
if(ret != 0) {
fprintf(stderr, "pthread_mutex_lock error:%s\n", strerror(ret));
exit(1);
}
printf("hello ");
sleep(rand() % 3);
printf("world\n");
ret = pthread_mutex_unlock(&mutex);//解锁
if(ret != 0) {
fprintf(stderr, "pthread_mutex_lock error:%s\n", strerror(ret));
exit(1);
}
sleep(rand() % 3);
}
return NULL;
}
int main() {
pthread_t tid;
int ret;
srand(time(NULL));
ret = pthread_mutex_init(&mutex, NULL);//初始化锁
if(ret != 0) {
fprintf(stderr, "pthread_mutex_init error:%s\n", strerror(ret));
exit(1);
}
ret = pthread_create(&tid, NULL, call_back, NULL);//创建子线程
if(ret != 0) {
fprintf(stderr, "pthread_create error:%s\n", strerror(ret));
exit(1);
}
while(1) {
ret = pthread_mutex_lock(&mutex);//加锁
if(ret != 0) {
fprintf(stderr, "pthread_mutex_lock error:%s\n", strerror(ret));
exit(1);
}
printf("HELLO ");
sleep(rand() % 3);
printf("WORLD\n");
ret = pthread_mutex_unlock(&mutex);//解锁
if(ret != 0) {
fprintf(stderr, "pthread_mutex_unlock error:%s\n", strerror(ret));
exit(1);
}
sleep(rand() % 3);
}
ret = pthread_mutex_destroy(&mutex);
if(ret != 0) {
fprintf(stderr, "pthread_mutex_destroy error:%s\n", strerror(ret));
exit(1);
}
return 0;
}
/*结果 达到理想效果
HELLO WORLD
HELLO WORLD
hello world
HELLO WORLD
HELLO WORLD
hello world
*/
注:在访问共享资源前加锁,访问结束后立即解锁。锁的“粒度”应越小越好。
2.3 死锁
使用锁不恰当导致的现象。
- 试图对同一个互斥量A加锁两次
- 线程1拥有A锁,请求获得B锁,线程2拥有B锁,请求获得A锁
3. 读写锁
与互斥量类似,但读写锁允许更高的并行性。其特性为:写独占,读共享。
3.1 读写锁的状态
特别注意:读写锁只有一把,但具备两种状态:
- 读模式下加锁状态(读锁)。
- 写模式下加锁状态(写锁),写锁优先级高。
3.2 读写锁的特性
- 读写锁是”写模式加锁“时,解锁前,所有对该锁加锁的线程都会阻塞。
- 读写锁是”读模式加锁“时,如果线程以读模式加锁,会成功;以写模式加锁,会阻塞。
- 读写锁是”读模式加锁“时,既有以写锁方式对该锁加锁,又有以读锁方式及对该锁进行加锁,那么读写锁会阻塞以读方式加锁的线程,优先满足写锁,因为:读写锁并行阻塞,写锁的优先级高。
读写锁适合对数据读的次数远大于写的次数,可以提高效率。
3.3 主要应用函数
3.3.1 pthread_rwlock_init 函数
函数作用:初始化读写锁。
函数原型:
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
函数参数:
rwlock:读写锁
attr:读写锁属性
返回值:
成功:0
失败:错误号
3.3.2 pthread_rwlock_destroy 函数
函数作用:销毁一把读写锁
函数原型:
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
函数参数:
rwlock:读写锁
返回值:
成功:0
失败:错误号
3.3.3 pthread_rwlock_rdlock 函数
函数作用:加读锁
函数原型:
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
函数参数:
rwlock:读写锁
返回值:
成功:0
失败:错误号
3.3.4 pthread_rwlock_wrlock 函数
函数作用:加写锁
函数原型:
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
函数参数:
rwlock:读写锁
返回值:
成功:0
失败:错误号
3.3.5 pthread_rwlock_tryrdlock 函数
函数作用:尝试添加读锁(非阻塞)
函数原型:
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
函数参数:
rwlock:读写锁
返回值:
成功:0
失败:错误号
3.3.6 pthread_rwlock_trywrlock 函数
函数作用:尝试添加写锁(非阻塞)
函数原型:
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
函数参数:
rwlock:读写锁
返回值:
成功:0
失败:错误号
3.3.7 pthread_rwlock_unlock 函数
函数作用:解锁
函数原型:
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
函数参数:
rwlock:读写锁
返回值:
成功:0
失败:错误号
3.3.8 读写锁示例
创建3个子线程写操作,创建5个子线程读操作,对全局变量var进行读写操作。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
int var = 100;
pthread_rwlock_t rwlock;
void* th_write(void* arg) {
int i = (int)arg;
int t;
int ret;
while(1) {
//添加写锁
ret = pthread_rwlock_wrlock(&rwlock);
if(ret != 0) {
fprintf(stderr, "wrlock error:%s\n", strerror(ret));
exit(1);
}
t = var;
usleep(1000);
printf("thread %dth write, tid = %lu, var = %d, ++var = %d\n", i + 1, pthread_self(), t, ++var);
//解锁
pthread_rwlock_unlock(&rwlock);
if(ret != 0) {
fprintf(stderr, "unlock error:%s\n", strerror(ret));
exit(1);
}
usleep(20000);
}
return NULL;
}
void* th_read(void* arg) {
int i = (int)arg;
int ret;
while(1) {
//添加读锁
pthread_rwlock_rdlock(&rwlock);
if(ret != 0) {
fprintf(stderr, "rwlock error:%s\n", strerror(ret));
exit(1);
}
printf("-------%dth read pthread, tid = %lu, var = %d\n", i + 1, pthread_self(), var);
//解锁
pthread_rwlock_unlock(&rwlock);
if(ret != 0) {
fprintf(stderr, "unlock error:%s\n", strerror(ret));
exit(1);
}
usleep(2000);
}
return NULL;
}
int main() {
pthread_t tid[8];
int ret;
int i;
//初始化锁
ret = pthread_rwlock_init(&rwlock, NULL);
if(ret != 0) {
fprintf(stderr, "init error:%s\n", strerror(ret));
exit(1);
}
//循环创建3个子线程,用于对var写操作
for(i = 0; i < 3; i++) {
ret = pthread_create(&tid[i], NULL, th_write, (void*)i);
if(ret != 0) {
fprintf(stderr, "pthrd_create error:%s\n", strerror(ret));
exit(1);
}
}
//循环创建5个子线程用于对var进行读操作
for(i = 0; i < 5; i++) {
ret = pthread_create(&tid[i + 3], NULL, th_read, (void*)i);
if(ret != 0) {
fprintf(stderr, "pthrd_create error:%s\n", strerror(ret));
exit(1);
}
}
//回收子线程
for(i = 0; i < 8; i++) {
ret = pthread_join(&tid[i], NULL);
if(ret != 0) {
fprintf(stderr, "join error:%s\n", strerror(ret));
exit(1);
}
}
//销毁读写锁
ret = pthread_rwlock_destroy(&rwlock);
if(ret != 0) {
fprintf(stderr, "pthread_rwlock_destroy error:%s\n", strerror(ret));
exit(1);
}
return 0;
}
4.条件变量
4.1 条件变量概述
条件变量本身不是锁,条件变量是用来等待线程的,条件变量通常和互斥锁一起使用。条件变量之所以要和互斥锁一起使用,主要是因为互斥锁的一个明显的特点就是它只有两种状态:锁定和非锁定,而条件变量可以通过允许线程阻塞和等待另一个线程发送信号来弥补互斥锁的不足,所以互斥锁和条件变量通常一起使用。
当条件满足的时候,线程通常解锁并等待该条件发生变化,一旦另一个线程修改了环境变量,就会通知相应的环境变量唤醒一个或者多个被这个条件变量阻塞的线程。这些被唤醒的线程将重新上锁,并测试条件是否满足。一般来说条件变量被用于线程间的同步;当条件不满足的时候,允许其中的一个执行流挂起和等待
4.2 主要应用函数
4.2.1 pthread_cond_init 函数
函数作用:初始化一个条件变量。
函数原型:
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
函数参数:
cond:条件变量
attr:条件变量属性,通常传NULL
返回值:
成功:0
失败:错误号
也可以使用静态初始化的方法,初始化条件变量:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
4.2.2 pthread_cond_wait 函数
函数作用:阻塞一个条件变量
1、阻塞等待条件变量cond(参1)满足
2、释放已掌握的互斥锁(解锁mutex)–相当于 pthread_mutex_unlock(&mutex);
操作
3、当被唤醒,pthread_cond_wait 函数返回时,解除阻塞,并重新申请获取互斥锁 pthread_mutex_lock(&mutex);
1,2两步为一个原子操作,不可分开
函数原型:
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
函数参数:
cond:条件变量
返回值:
成功:0
失败:错误号
4.2.3 pthread_cond_timedwait 函数
函数作用:限时等待一个条件变量
函数原型:
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
函数参数:
cond:条件变量
mutex:互斥量
abstime:绝对时间
返回值:
成功:0
失败:错误号
4.2.4 pthread_cond_signal 函数
函数作用:唤醒阻塞在条件变量上的(至少)一个线程
函数原型:
int pthread_cond_signal(pthread_cond_t *cond);
函数参数:
cond:条件变量
返回值:
成功:0
失败:错误号
4.2.5 pthread_cond_broadcast 函数
函数作用:唤醒全部阻塞在条件变量上的线程
函数原型:
int pthread_cond_broadcast(pthread_cond_t *cond);
函数参数:
cond:条件变量
返回值:
成功:0
失败:错误号
4.2.6 pthread_cond_destroy 函数
函数作用:销毁一个条件变量)
函数原型:
int pthread_cond_destroy(pthread_cond_t *cond);
函数参数:
cond:条件变量
返回值:
成功:0
失败:错误号
4.3 生产者、消费者模型
主线程创建两个线程,一个生产产品,一个消费产品。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
struct msg {
int num;
struct msg* next;
};
struct msg *head;//共享区域
//定义互斥锁和条件变量并初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
//初始化锁和条件变量
//mutex = PTHREAD_MUTEX_INITIALIZER;
//cond = PTHREAD_COND_INITIALIZER;
//生产者
void* producer(void* p) {
struct msg* mp;
int ret;
while(1) {
//生产者生产产品
mp = malloc(sizeof(struct msg));
mp->num = rand() % 100 + 1;
mp->next = NULL;
printf("producer: mp->num = %d\n", mp->num);
ret = pthread_mutex_lock(&mutex); // 加锁,获取对共享区域的操作权力
if(ret != 0) {
fprintf(stderr, "pth_mutex_lock:%s\n", strerror(ret));
exit(1);
}
//将生产的产品加入到共享区域
mp->next = head;
head = mp;
//解锁,失去对共享区域的使用权
ret = pthread_mutex_unlock(&mutex);
if(ret != 0) {
fprintf(stderr, "pth_mutex_unlock:%s\n", strerror(ret));
exit(1);
}
//给消费者发信号,唤醒阻塞在pthread_cond_wait函数的消费者
ret = pthread_cond_signal(&cond);
if(ret != 0) {
fprintf(stderr, "pth_cond_signal:%s\n", strerror(ret));
exit(1);
}
usleep((rand() % 5 + 1) * 1000);
}
return NULL;
}
//消费者
void* consumer(void* s) {
struct msg* mc;
int ret;
while(1) {
//加锁,获取对共享区域的操作权
ret = pthread_mutex_lock(&mutex);
if(ret != 0) {
fprintf(stderr, "pth_mutex_lock:%s\n", strerror(ret));
exit(1);
}
//如果不满足条件,释放互斥锁(解锁),阻塞等待条件满足,被唤醒时重新加锁
while(head == NULL) {
ret = pthread_cond_wait(&cond, &mutex);
if(ret != 0) {
fprintf(stderr, "pthread_cond_wait error:%s\n", strerror(ret));
exit(1);
}
}
//从共享区域消费物品
mc = head;
head = mc->next;
//解锁,失去对共享区域的操作权
ret = pthread_mutex_unlock(&mutex);
if(ret != 0) {
fprintf(stderr, "pth_mutex_unlock:%s\n", strerror(ret));
exit(1);
}
printf("------------------consumer: mc->num = %d\n", mc->num);
free(mc);//释放删除内容占用的的内存空间
usleep((rand() % 5 + 1) * 1000);
}
return NULL;
}
int main() {
pthread_t tid1, tid2;
int ret;
srand(time(NULL));
//初始化条件变量和锁
//pthread_cond_init(&cond, NULL);
//pthread_mutex_init(&mutex, NULL);
//创建生产者、消费者线程
ret = pthread_create(&tid1, NULL, producer, NULL);
if(ret != 0) {
fprintf(stderr, "pth_create error:%s\n", strerror(ret));
exit(1);
}
ret = pthread_create(&tid2, NULL, consumer, NULL);
if(ret != 0) {
fprintf(stderr, "pth_create error:%s\n", strerror(ret));
exit(1);
}
//回收生产者、消费者线程
ret = pthread_join(tid1, NULL);
if(ret != 0) {
fprintf(stderr, "pth_join error:%s\n", strerror(ret));
exit(1);
}
ret = pthread_join(tid2, NULL);
if(ret != 0) {
fprintf(stderr, "pth_join error:%s\n", strerror(ret));
exit(1);
}
return 0;
}
5.信号量
进化版的互斥锁。(相当于初始化值为 N 的互斥量,N表示同时访问共享区域的线程最大数量)
由于互斥锁的粒度比较大,如果我们希望在多个线程间对某一对象的部分数据进行共享,使用互斥锁是没有办法实现的,只能将整个数据对象锁住。这样虽然达到了多线程操作共享数据时保证数据的正确性的目的,却无形中导致线程的并发性下降,线程从并行执行,变成了串行执行。与直接使用单进程无异。
信号量,是相对折中的一种处理方式,既能保证同步,数据不混乱,又能提高线程并发。
5.1 主要应用函数
5.1.1 sem_init 函数
函数作用:初始化一个信号量(信号量初值 N,决定了占用信号量的线程的个数)
函数原型:
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
函数参数:
sem:信号量 sem值应 >0
pshared:
0:表示线程间同步
非0:进程间同步(通常传 1)
value:N值(指定同时访问的线程个数)
返回值:
成功:0
失败:错误号
5.1.2 sem_destroy 函数
函数作用:销毁一个信号量
函数原型:
#include <semaphore.h>
int sem_destroy(sem_t *sem);
函数参数:
sem:信号量
返回值:
成功:0
失败:错误号
5.1.3 sem_wait 函数
函数作用:加锁,可以看作 N--
操作,当 N 减为 0,就会阻塞线程
函数原型:
#include <semaphore.h>
int sem_wait(sem_t *sem);
函数参数:
sem:信号量
返回值:
成功:0
失败:错误号
5.1.4 sem_post 函数
函数作用:解锁,可以看作 N++
操作,
函数原型:
#include <semaphore.h>
int sem_init(sem_t *sem);
函数参数:
sem:信号量
返回值:
成功:0
失败:错误号
5.1.5 sem_trywait 函数
函数作用:非阻塞加锁,可以看作 N--
操作,当 N 减为 0,就会直接返回
函数原型:
#include <semaphore.h>
int sem_init(sem_t *sem);
函数参数:
sem:信号量
返回值:
成功:0
失败:错误号
5.1.6 sem_timedwait 函数
函数作用:定时阻塞加锁,可以看作 N--
操作,当 N 减为 0,就会阻塞,超时后返回
函数原型:
#include <semaphore.h>
int sem_init(sem_t *sem, const struct tniespec *abs_timeout);
函数参数:
sem:信号量
abs_timeout:时间,设置时间后,到达时间返回
返回值:
成功:0
失败:错误号
5.2 信号量实现生产者、消费者模型
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#include <semaphore.h>
#define N 5
int queue[N];
sem_t pro_sem, con_sem;
void* pro_back(void* arg) { //生产者
int ret;
int i = 0;
while(1) {
ret = sem_wait(&pro_sem);//如果空位等于0,阻塞
if(ret != 0) {
fprintf(stderr, "sem_wait error:%s\n", strerror(ret));
exit(1);
}
queue[i] = rand() % 100 + 1;
printf("produce: %d\n", queue[i]);
ret = sem_post(&con_sem);
if(ret != 0) {
fprintf(stderr, "sem_post error:%s\n", strerror(ret));
exit(1);
}
i = (i + 1) % N;
sleep(rand() % 1);
}
return NULL;
}
void* con_back(void* arg) {
int ret;
int i = 0;
while(1) {
ret = sem_wait(&con_sem);
if(ret != 0) {
fprintf(stderr, "sem_wait error:%s\n", strerror(ret));
exit(1);
}
printf("----------------consume:%d\n", queue[i]);
queue[i] = 0;
ret = sem_post(&pro_sem);
if(ret != 0) {
fprintf(stderr, "sem_post error:%s\n", strerror(ret));
exit(1);
}
i = (i + 1) % N;
sleep(rand() % 3);
}
return NULL;
}
int main() {
pthread_t tid1, tid2;
int ret;
srand(time(NULL));
ret = sem_init(&pro_sem, 0, N);
if(ret != 0) {
fprintf(stderr, "init error:%s\n", strerror(ret));
exit(1);
}
ret = sem_init(&con_sem, 0, 0);
if(ret != 0) {
fprintf(stderr, "init error:%s\n", strerror(ret));
exit(1);
}
ret = pthread_create(&tid1, NULL, pro_back, NULL);
if(ret != 0) {
fprintf(stderr, "pth_cre error:%s\n", strerror(ret));
exit(1);
}
ret = pthread_create(&tid2, NULL, con_back, NULL);
if(ret != 0) {
fprintf(stderr, "pth_cre error:%s\n", strerror(ret));
exit(1);
}
ret = pthread_join(tid1, NULL);
if(ret != 0) {
fprintf(stderr, "pth_join error:%s\n", strerror(ret));
exit(1);
}
ret = pthread_join(tid2, NULL);
if(ret != 0) {
fprintf(stderr, "pth_join error:%s\n", strerror(ret));
exit(1);
}
ret = sem_destroy(&pro_sem);
if(ret != 0) {
fprintf(stderr, "sem_des error:%s\n", strerror(ret));
exit(1);
}
ret = sem_destroy(&con_sem);
if(ret != 0) {
fprintf(stderr, "sem_des error:%s\n", strerror(ret));
exit(1);
}
return 0;
}