Linux线程管理实验——互斥锁和条件变量

1.互斥锁基本原理

       互斥锁提供了对临界资源以互斥方式进行访问的同步机制。简单来说,互斥锁类似于一个布尔变量,它只有“锁定”和“打开”两种状态,在使用临界资源时线程先申请互斥锁,如果此时互斥锁处于“打开”状态,则立刻占有该锁,将状态置为“锁定”。此时如果再有其他线程使用该临界资源时发现互斥锁处于“锁定”状态,则阻塞该线程,直到持有该互斥锁的线程释放该锁。通过这样的机制保证在使用临界资源时数据不会被另外一个线程破坏。

2.条件变量

       在对线程进行操作时需要在一定条件下互斥地访问临界资源。比如有一个缓存区,当缓存区为空时A线程向其中写入数据,当缓存区中有数据时B线程将其取出并打印在屏幕上。这种情况下,单纯地使用互斥锁显然无法完全满足要求。这个时候就需要使用Linux提供的另一个同步机制——条件变量。

3.条件变量基本原理

       条件变量类似于if语句,它有“真”“假”两个状态。在条件变量的使用过程中一个线程等待条件变为“真”,另一个线程在使用完临界资源之后将条件设置为“真”,然后唤醒阻塞等待条件变量为“真”的线程,执行其任务。在这个过程中必须保证在并发/并行的条件下使得条件变量“真”“假”状态正确转换,所以条件变量一般需要和互斥锁配合使用实现对资源的互斥访问。

4.实验内容

       创建两个子线程:一个子线程(生产者线程)依次向缓冲区写入整数0,1,2,…,19;另一个子线程(消费者线程)暂停3s后,从缓冲器读数,每次读一个,并将读出的数字从缓冲区删除,然后将数字显示出来;父线程等待子线程2(消费者线程)的退出信息,待收集到该信息后,父线程就返回。

代码:
       下面的代码不是完全符合题目要求,不一样的地方在于:消费者进程是一次性将数据读出并显示在屏幕,然后一次性将数据删除。改进版的代码在后面。

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<string.h>
#include<unistd.h>

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(&notempty);   //等带缓冲区非空
        pthread_mutex_unlock(&mutex);

        return 0;
}

void * consume(void * arg)
{
        int i;
        printf("consume is running!\n");
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&notempty,&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(&notempty,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(&notempty); //销毁条件变量

        return 0;
}

编译并运行:
gcc -g condition.c -o condition -lpthread
./condition
注意,一定要加-lpthread,否则编译会出错。
在这里插入图片描述
       两个子线程并发运行,因为缓冲区为空,所以消费者进程被阻塞,生产者进程开始执行,写入数据,写完后缓冲区不为空,消费者线程开始执行,读出数据并将缓冲区清空。

       上面的代码是一次性读完和一次性删除,下面的代码是生产者线程写一个数,然后转到消费者线程读取并删除一个数,然后再转到生产者线程写,…,以此类推,总共写十九个数读并删除十九个数,但是有一点,就是在写完一个数后,间隔1s(sleep(1))然后再读并删除一个数,而题目要求是间隔3s(sleep(3)),我在使用sleep(3)时程序会莫名其妙的卡住,而用sleep(1)时会完整的执行完程序,具体原因尚不明确,以后弄清楚再更新。

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<string.h>
#include<unistd.h>

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(&notempty);   //等带缓冲区非空
                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(&notempty,&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(&notempty,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(&notempty); //销毁条件变量

        return 0;
}

5.代码中关于互斥锁和条件变量的接口

①互斥锁的初始化(pthread_mutex_init函数)

函数名称pthread_mutex_init
函数功能初始化互斥锁
头文件#include<pthread.h>
函数原型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<pthread.h>
函数原型int pthread_mutex_destroy(pthread_mutex_t * mutex);
参数mutex:需要销毁的互斥对象的指针
返回值0:成功。 非0:失败

③申请互斥锁(pthread_mutex_lock函数)

函数名称pthread_mutex_lock
函数功能申请互斥锁
头文件#include<pthread.h>
函数原型int pthread_mutex_lock(pthread_mutex_t * mutex);
参数mutex:指向互斥锁对象的指针
返回值0:成功。 非0:失败。

④释放互斥锁(pthread_mutex_unlock函数)

函数名称pthread_mutex_unlock
函数功能释放互斥锁
头文件#include<pthread.h>
函数原型int pthread_mutex_unlock(pthread_mutex_t * mutex);
参数mutex:指向互斥锁对象的指针
返回值0:成功。 非0:失败。

⑤条件变量的初始化(pthread_cond_init函数)

函数名称pthread_cond_init
函数功能初始化条件变量
头文件#include<pthread.h>
函数原型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<pthread.h>
函数原型int pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t * mutex);
参数cond:需要等待的条件变量。 mutex:与条件变量相关的互斥锁。
返回值0:成功。 非0:失败。

⑦通知等待该条件变量的线程(pthread_cond_signal函数)

函数名称pthread_cond_signal
函数功能唤醒一个等待线程
头文件#include<pthread.h>
函数原型int pthread_cond_signal(pthread_cond_t * cond);
参数cond:需要通知的条件变量的指针。
返回值0:成功。 非0:失败。

⑧销毁条件变量(pthread_cond_destroy函数)

函数名称pthread_cond_destroy
函数功能销毁条件变量
头文件#include<pthread.h>
函数原型int pthread_cond_destroy(pthread_cond_t * cond);
参数cond:需要销毁的条件变量
返回值0:成功。 非0:失败。
  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 作来实现生产者和消费者的同步和互斥。 为了实现同步和互斥,我们可以使用三个信号量:empty、full和mutex。其中,empty表示空缓冲区的数量,full表示满缓冲区的数量,mutex用于实现互斥。 生产者进程的伪代码如下: ``` while (true) { // 生产者等待至少有3个空缓冲区 wait(empty, 3); // 生产者获取互斥 wait(mutex); // 生产者向3个缓冲区写入整数 write_to_buffer(); // 生产者释放互斥 signal(mutex); // 生产者通知消费者有新数据可用 signal(full, 3); } ``` 消费者进程的伪代码如下: ``` while (true) { // 消费者等待至少有1个满缓冲区 wait(full, 1); // 消费者获取互斥 wait(mutex); // 消费者从缓冲区取出整数 read_from_buffer(); // 消费者释放互斥 signal(mutex); // 消费者通知生产者有空缓冲区可用 signal(empty, 1); } ``` 在上述代码中,wait表示P操作,signal表示V操作。当empty或full的值为0时,wait操作会阻塞进程,直到有足够的空或满缓冲区可用。mutex用于保证同一时间只有一个进程可以访问缓冲区。 ### 回答2: 在这个问题中,有多个生产者进程将数据存储在缓冲区中,同时有多个消费者进程从缓冲区中取出数据。由于多个进程同时访问缓冲区,需要使用信号量来实现同步和互斥。 首先,我们需要定义三个信号量:empty,表示空的缓冲区数量;full,表示已经写入数据的缓冲区数量;mutex,用于保证进程之间的互斥访问。 接下来,我们可以使用P操作和V操作来实现信号量的操作: P(empty):判断是否有空的缓冲区可用,如果有,则empty-1,否则等待。 P(mutex):进入临界区,用于保证缓冲区的互斥访问。 写入数据:将数据存储在每个空的缓冲区中,然后将full+1。 V(mutex):退出临界区。 V(full):表示已经填充好了缓冲区,full+1。 对于消费者进程: P(full):判断是否有填充好的缓冲区可以使用,如果没有则等待。 P(mutex):进入临界区。 读取数据:从一个填充好的缓冲区中读取数据,然后将full-1。 V(mutex):退出临界区。 V(empty):表示已空缓冲区数量,empty+1。 需要注意的是,这种解决方案可能会导致死问题。例如,在生产者进程填充了所有缓冲区但消费者进程还没有读取它们的情况下,程序会使所有进程都停滞。因此,需要考虑如何在等待时间后禁用某些缓冲区并在稍后启用它们。这可以通过设置超时值来实现。 通过使用信号量和P、V操作,我们可以实现生产者和消费者模型。这种模型可以在多个进程之间共享数据,从而提高系统的并发性和效率。 ### 回答3: 实现这个场景需要使用两个信号量:一个信号量用于表示缓冲区是否可写,另一个信号量用于表示缓冲区是否可读。 首先,我们需要定义三个变量:buffer是一个二维数组,用于表示存储整数的缓冲区;in和out分别是指向缓冲区的指针,用于指示生产者或消费者写入或读取数据的位置。 初始化时,需要将两个信号量都初始化为9,表示所有的缓冲区都是可写和可读的。生产者进程执行write操作时,需要P操作缓冲区可写信号量,判断当前是否有足够的缓冲区可写,若可写则继续执行,不可写则等待。进入写操作的生产者进程将 in 指针数据写入缓冲区,并且 in 指向下一个缓冲区,同时将缓冲区可读信号量V,表示已经写入了一个整数,此时缓冲区可读信号量加1。 消费者进程执行 read 操作时,需要P操作缓冲区可读信号量,判断当前是否有缓冲区可读,若可读则继续执行,否则等待。进入读操作的消费者进程将 out 指针数据读出,同时 out 指针指向下一个缓冲区,表示此时已经读出一个整数,将缓冲区可写操作V,表示缓冲区现在可写。 当所有的缓冲区都被写入或读取时,生产者和消费者必须等待,直到缓冲区中的数据被消费或写入。为了实现此功能,我们可以使用一个计数器,追踪已经产生或消费的项目数量,当这个计数器达到 9 时,所有的生产者和消费者都将被阻塞。如果缓冲区中有多个空闲缓冲区,则可以将这个计数器的初始值设置为(9 – 窗口大小)。每当生产者写入一个整数或消费者读取一个整数时,该计数器就会增加或减小一个。如果缓冲区满,它们会阻塞,直到一个计数器值为0 为止。如果缓冲区为空,则它们也会阻塞,直到计数器的值恢复到它们窗口的大小为止。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值