互斥锁提供了对临界资源以互斥方式进行访问的同步机制。简单来说,互斥锁类似于一个布尔变量,它只有“锁定”和“打开”两种状态,在使用临界资源时线程先申请互斥锁,如果此时互斥锁处于“打开”状态,则立刻占有该锁,将状态置为“锁定”。此时如果再有其他线程使用该临界资源时发现互斥锁处于“锁定”状态,则阻塞该线程,直到持有该互斥锁的线程释放该锁。通过这样的机制保证在使用临界资源时数据不会被另外一个线程破坏。
在对线程进行操作时需要在一定条件下互斥地访问临界资源。比如有一个缓存区,当缓存区为空时A线程向其中写入数据,当缓存区中有数据时B线程将其取出并打印在屏幕上。这种情况下,单纯地使用互斥锁显然无法完全满足要求。这个时候就需要使用Linux提供的另一个同步机制——条件变量。
条件变量类似于if语句,它有“真”“假”两个状态。在条件变量的使用过程中一个线程等待条件变为“真”,另一个线程在使用完临界资源之后将条件设置为“真”,然后唤醒阻塞等待条件变量为“真”的线程,执行其任务。在这个过程中必须保证在并发/并行的条件下使得条件变量“真”“假”状态正确转换,所以条件变量一般需要和互斥锁配合使用实现对资源的互斥访问。
创建两个子线程:一个子线程(生产者线程)依次向缓冲区写入整数0,1,2,…,19;另一个子线程(消费者线程)暂停3s后,从缓冲器读数,每次读一个,并将读出的数字从缓冲区删除,然后将数字显示出来;父线程等待子线程2(消费者线程)的退出信息,待收集到该信息后,父线程就返回。
代码:
下面的代码不是完全符合题目要求,不一样的地方在于:消费者进程是一次性将数据读出并显示在屏幕,然后一次性将数据删除。改进版的代码在后面。
#include
#include
#include
#include
#include
pthread_mutex_t mutex; //互斥锁
pthread_cond_t empty; //为空的条件变量
pthread_cond_t notempty; //非空的条件变量
int buf[20];
void *producer(void * arg)
{
int i;
printf("producer is running!\n");
pthread_mutex_lock(&mutex); //申请互斥锁
pthread_cond_wait(&empty, &mutex); //等待缓存区为空
for(i=1; i<20; i++)
memcpy(&buf[i-1], &i, 4); //写入数据
printf("已写入数据。\n");
pthread_cond_signal(¬empty); //等带缓冲区非空
pthread_mutex_unlock(&mutex);
return 0;
}
void * consume(void * arg)
{
int i;
printf("consume is running!\n");
pthread_mutex_lock(&mutex);
pthread_cond_wait(¬empty,&mutex); //等待缓冲区不为空
sleep(3);
printf("读出数据:\n");
for(i=0; i<19; i++)
printf("%d ",buf[i]);
printf("\n");
memset(buf, 0, 80);
printf("已清除数据。\n");
sleep(1);
pthread_cond_signal(&empty);
pthread_mutex_unlock(&mutex);
return 0;
}
int main(char argc, char *argv[])
{
pthread_t thid1,thid2;
pthread_mutex_init(&mutex,NULL); //初始化互斥锁
pthread_cond_init(&empty,NULL); //初始化为空时的条件变量
pthread_cond_init(¬empty,NULL); //初始化不为空的条件变量
pthread_create(&thid1, NULL, producer, NULL);
pthread_create(&thid2, NULL, consume, NULL);
sleep(1);
pthread_cond_signal(&empty); //为空
int *ret1, *ret2;
pthread_join(thid1, (void * * )&ret1);
pthread_join(thid2, (void * * )&ret2);
pthread_mutex_destroy(&mutex); //销毁互斥量
pthread_cond_destroy(&empty); //销毁条件变量
pthread_cond_destroy(¬empty); //销毁条件变量
return 0;
}
编译并运行:
gcc -g condition.c -o condition -lpthread
./condition
注意,一定要加-lpthread,否则编译会出错。
两个子线程并发运行,因为缓冲区为空,所以消费者进程被阻塞,生产者进程开始执行,写入数据,写完后缓冲区不为空,消费者线程开始执行,读出数据并将缓冲区清空。
上面的代码是一次性读完和一次性删除,下面的代码是生产者线程写一个数,然后转到消费者线程读取并删除一个数,然后再转到生产者线程写,…,以此类推,总共写十九个数读并删除十九个数,但是有一点,就是在写完一个数后,间隔1s(sleep(1))然后再读并删除一个数,而题目要求是间隔3s(sleep(3)),我在使用sleep(3)时程序会莫名其妙的卡住,而用sleep(1)时会完整的执行完程序,具体原因尚不明确,以后弄清楚再更新。
#include
#include
#include
#include
#include
pthread_mutex_t mutex; //互斥锁
pthread_cond_t empty; //为空的条件变量
pthread_cond_t notempty; //非空的条件变量
int buf[20];
int temp=0;
void *producer(void * arg)
{
int i;
for(i=1; i<20; i++)
{
printf("producer is running!\n");
pthread_mutex_lock(&mutex); //申请互斥锁
pthread_cond_wait(&empty, &mutex); //等待缓存区为空
memcpy(&buf[i-1], &i, 4); //写入数据
printf("已写入数据:%d\n",i);
pthread_cond_signal(¬empty); //等带缓冲区非空
pthread_mutex_unlock(&mutex);
}
}
void * consume(void * arg)
{
int i;
for(i=0; i<19; i++)
{
printf("consume is running!\n");
pthread_mutex_lock(&mutex);
pthread_cond_wait(¬empty,&mutex); //等待缓冲区不为空
sleep(1);
printf("读出数据:buf[%d]=%d\n",i,buf[i]);
printf("\n");
temp = buf[i];
memset(&buf[i], 0, 4);
printf("已清除数据。buf[%d]:%d\n",i,temp);
pthread_cond_signal(&empty);
pthread_mutex_unlock(&mutex);
}
}
int main(char argc, char *argv[])
{
pthread_t thid1,thid2;
pthread_mutex_init(&mutex,NULL); //初始化互斥锁
pthread_cond_init(&empty,NULL); //初始化为空时的条件变量
pthread_cond_init(¬empty,NULL); //初始化不为空的条件变量
pthread_create(&thid1, NULL, producer, NULL);
pthread_create(&thid2, NULL, consume, NULL);
sleep(1);
pthread_cond_signal(&empty); //为空
int *ret1, *ret2;
pthread_join(thid1, (void * * )&ret1);
pthread_join(thid2, (void * * )&ret2);
pthread_mutex_destroy(&mutex); //销毁互斥量
pthread_cond_destroy(&empty); //销毁条件变量
pthread_cond_destroy(¬empty); //销毁条件变量
return 0;
}
①互斥锁的初始化(pthread_mutex_init函数)
函数名称 | pthread_mutex_init |
---|---|
函数功能 | 初始化互斥锁 |
头文件 | #include |
函数原型 | int pthread_mutex_init(pthread_mutex_t * restrict mutex, const pthread_mutexattr_t * restrict attr); |
参数 | mutex:需要被初始化的互斥锁指针。 attr:指向描述互斥锁属性的指针。 |
返回值 | 0:成功。 非0:失败 |
其中restrict 是C语言中的一个关键字。
②互斥锁的销毁(pthread_mutex_destroy函数)
函数名称 | pthread_mutex_destroy |
---|---|
函数功能 | 销毁互斥锁 |
头文件 | #include |
函数原型 | int pthread_mutex_destroy(pthread_mutex_t * mutex); |
参数 | mutex:需要销毁的互斥对象的指针 |
返回值 | 0:成功。 非0:失败 |
③申请互斥锁(pthread_mutex_lock函数)
函数名称 | pthread_mutex_lock |
---|---|
函数功能 | 申请互斥锁 |
头文件 | #include |
函数原型 | int pthread_mutex_lock(pthread_mutex_t * mutex); |
参数 | mutex:指向互斥锁对象的指针 |
返回值 | 0:成功。 非0:失败。 |
④释放互斥锁(pthread_mutex_unlock函数)
函数名称 | pthread_mutex_unlock |
---|---|
函数功能 | 释放互斥锁 |
头文件 | #include |
函数原型 | int pthread_mutex_unlock(pthread_mutex_t * mutex); |
参数 | mutex:指向互斥锁对象的指针 |
返回值 | 0:成功。 非0:失败。 |
⑤条件变量的初始化(pthread_cond_init函数)
函数名称 | pthread_cond_init |
---|---|
函数功能 | 初始化条件变量 |
头文件 | #include |
函数原型 | int pthread_cond_init(pthread_cond_t * cv, const pthread_condattr_t * cattr); |
参数 | cv:指向要初始化的条件变量的指针。 cattr:指向条件变量属性的指针,指定条件变量的一些属性,如果为NULL则表明使用默认属性初始化该条件变量。 |
返回值 | 0:成功。 非0:失败。 |
⑥阻塞等待条件变量(pthread_cond_wait函数)
函数名称 | pthread_cond_wait |
---|---|
函数功能 | 等待条件变量被设置 |
头文件 | #include |
函数原型 | int pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t * mutex); |
参数 | cond:需要等待的条件变量。 mutex:与条件变量相关的互斥锁。 |
返回值 | 0:成功。 非0:失败。 |
⑦通知等待该条件变量的线程(pthread_cond_signal函数)
函数名称 | pthread_cond_signal |
---|---|
函数功能 | 唤醒一个等待线程 |
头文件 | #include |
函数原型 | int pthread_cond_signal(pthread_cond_t * cond); |
参数 | cond:需要通知的条件变量的指针。 |
返回值 | 0:成功。 非0:失败。 |
⑧销毁条件变量(pthread_cond_destroy函数)
函数名称 | pthread_cond_destroy |
---|---|
函数功能 | 销毁条件变量 |
头文件 | #include |
函数原型 | int pthread_cond_destroy(pthread_cond_t * cond); |
参数 | cond:需要销毁的条件变量 |
返回值 | 0:成功。 非0:失败。 |