【Linux系统编程】条件变量

条件变量

        条件变量,通知状态的改变。条件变量允许一个线程就某个共享变量的状态变化通知其他线程,并让其他线程等待(阻塞于)这一通知。

​        条件变量总是结合互斥量使用。条件变量就共享变量的状态改变发出通知,而互斥量则提供对该共享变量访问的互斥。

演示程序:

​        定义一个变量a,线程1当a为0时对a+1,线程2当a为1时对a-1,循环10次,a的结果应该是0和1交替。

​        两个线程需要不断的轮询结果,造成CPU浪费。可以使用条件变量解决:线程1不满足运行条件时,先休眠等待,其他线程运行到满足线程1的运行条件时,通知并唤醒线程⒉继续执行。

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<errno.h>
#include<stdlib.h>
#include<time.h>

pthread_rwlock_t mtx;//定义读写锁
int a = 0;

void* thread1(void* arg) {
    for(int i = 0; i < 10; i++) {
        while(1) {
            pthread_mutex_lock(&mtx);
            if(a == 0) {
                a++;
                printf("a = %d\n", a);
                pthread_mutex_unlock(&mtx);
                break;
            }
            pthread_mutex_unlock(&mtx);
        }
    }
}

void* thread2(void* arg) {
    for(int i = 0; i < 10; i++) {
        while(1) {
            pthread_mutex_lock(&mtx);
            if(a == 1) {
                a--;
                printf("a = %d\n", a);
                pthread_mutex_unlock(&mtx);
                break;
            }
            pthread_mutex_unlock(&mtx);
        }
    }
}

int main(int argc, char* argv[])
{
    pthread_mutex_init(&mtx, NULL);

    pthread_t tid1;
    pthread_t tid2;
    pthread_create(&tid1, NULL, thread1, NULL);
    pthread_create(&tid2, NULL, thread2, NULL);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    return 0;
}

但是会造成CPU浪费,通过条件变量实现通知

条件变量函数

●int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);

        参数attr表示条件变量的属性,默认值传NULL

●int pthread_cond_destroy(pthread_cond_t *cond);

●int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

        阻塞等待一个条件变量,释放持有的互斥锁,这两步是原子操作

        被唤醒时,函数返回,解除阻塞并重新申请获取互斥锁

●int pthread_cond_signal(pthread_cond_t *cond);

        唤醒一个阻塞在条件变量上的线程

●int pthread_cond_broadcast(pthread_cond_t *cond);

        唤醒全部阻塞在条件变量上的线程

● int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t *mutex,
const struct timespec*abstime);

        限时等待一个条件变量

        参数abstime是一个timespec结构体,以秒和纳秒表示的绝对时间

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<errno.h>
#include<stdlib.h>
#include<time.h>

pthread_rwlock_t mtx;
pthread_cond_t cond;
int a = 0;

void* thread1(void* arg) {
    for(int i = 0; i < 10; i++) {
        pthread_mutex_lock(&mtx);
        if(a != 0) {
            pthread_cond_wait(&cond, &mtx);//阻塞等待,释放锁,被唤醒时重新抢锁
        }
        a++;
        printf("a = %d\n", a);
        pthread_cond_broadcast(&cond);
        pthread_mutex_unlock(&mtx);
    }
}

void* thread2(void* arg) {
    for(int i = 0; i < 10; i++) {
        pthread_mutex_lock(&mtx);
        if(a != 1) {
            pthread_cond_wait(&cond, &mtx);
        }
        a--;
        printf("a = %d\n", a);
        pthread_cond_broadcast(&cond);
        pthread_mutex_unlock(&mtx);
    }
}

int main(int argc, char* argv[])
{
    pthread_mutex_init(&mtx, NULL);
    pthread_cond_init(&cond, NULL);

    pthread_t tid1;
    pthread_t tid2;
    pthread_create(&tid1, NULL, thread1, NULL);
    pthread_create(&tid2, NULL, thread2, NULL);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    return 0;
}

从主动通知代替了轮询

虚假唤醒:不该唤醒的时候唤醒了。

pthread_cond_wait外面如果使用if 判断,再增加两个线程,会出现虚假唤醒

1.假设线程1抢到了锁,加1,通知所有线程抢锁

2.线程⒉抢到了锁,等待并释放锁

3.线程3抢到了锁,减1,通知所有线程抢锁

4.线程1抢到了锁,等待并释放锁

5.注意这里,如果线程3抢到了锁,wait结束,线程继续执行,+1,number变成2

6.number出现问题,不是0和1了,原因是wait结束时没有再次判断number的值

pthread_cond_wait外面使用循环判断,防止出现虚假唤醒

所有的if都换成while就解决了

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<errno.h>
#include<stdlib.h>
#include<time.h>
//同样操作的线程只有一个才没事
pthread_rwlock_t mtx;
pthread_cond_t cond;
int a = 0;

void* thread1(void* arg) {
    for(int i = 0; i < 10; i++) {
        pthread_mutex_lock(&mtx);
        while(a != 0) {
            pthread_cond_wait(&cond, &mtx);//阻塞等待,释放锁,被唤醒时重新抢锁
        }
        a++;
        printf("a = %d\n", a);
        pthread_cond_broadcast(&cond);
        pthread_mutex_unlock(&mtx);
    }
}

void* thread2(void* arg) {
    for(int i = 0; i < 10; i++) {
        pthread_mutex_lock(&mtx);//一个线程加锁,所有线程都阻塞在lock上
        while(a != 1) {
            pthread_cond_wait(&cond, &mtx);
        }
        a--;
        printf("a = %d\n", a);
        pthread_cond_broadcast(&cond);
        pthread_mutex_unlock(&mtx);
    }
}

void* thread3(void* arg) {
    for(int i = 0; i < 10; i++) {
        pthread_mutex_lock(&mtx);
        while(a != 0) {
            pthread_cond_wait(&cond, &mtx);//阻塞等待,释放锁,被唤醒时重新抢锁
        }
        a++;
        printf("a = %d\n", a);
        pthread_cond_broadcast(&cond);
        pthread_mutex_unlock(&mtx);
    }
}

void* thread4(void* arg) {
    for(int i = 0; i < 10; i++) {
        pthread_mutex_lock(&mtx);
        while(a != 1) {
            pthread_cond_wait(&cond, &mtx);
        }
        a--;
        printf("a = %d\n", a);
        pthread_cond_broadcast(&cond);
        pthread_mutex_unlock(&mtx);
    }
}

int main(int argc, char* argv[])
{
    pthread_mutex_init(&mtx, NULL);
    pthread_cond_init(&cond, NULL);

    pthread_t tid1;
    pthread_t tid2;
    pthread_t tid3;
    pthread_t tid4;
    pthread_create(&tid1, NULL, thread1, NULL);
    pthread_create(&tid2, NULL, thread2, NULL);
    pthread_create(&tid3, NULL, thread3, NULL);
    pthread_create(&tid4, NULL, thread4, NULL);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);
    pthread_join(tid4, NULL);
    return 0;
}

生产者消费者模型

​        线程同步典型的案例即为生产者消费者模型,而借助条件变量来实现这一模型,是比较常见的一种方法。假定有两个线程,一个模拟生产者行为,一个模拟消费者行为。两个线程同时操作一个共享资源(一般称之为汇聚),生产者向其中添加产品,消费者从中消费掉产品。

        相较于互斥量而言,条件变量可以减少竞争。如直接使用互斥量,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争互斥量是无意义的。有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序效率。

场景:一个线程产生随机数放入链表中,一个线程从链表中取出一个随机数打印

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<errno.h>
#include<stdlib.h>
#include<time.h>

pthread_mutex_t mtx;
pthread_cond_t cond;
int a = 0;

struct Node {
    int number;
    struct Node* next;
};
struct Node* head;
 
struct Node* append_node(int number) {//添加节点
    struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));
    new_node->number = number;
    new_node->next = head;
    head = new_node;
    return head;
}

int pop_head_node() {//移除头结点
    if (head == NULL) {
        return -1;
    }
    struct Node* temp = head;
    int number = temp->number;
    head = head->next;
    free(temp);
    return number;
}

void* producer(void* arg) {
    while(1) {
        pthread_mutex_lock(&mtx);
        append_node(rand() % 500 + 1);//产生随机数放到链表里
        pthread_cond_broadcast(&cond);//加一个唤醒
        pthread_mutex_unlock(&mtx);
        sleep(1);
    }
}

void* customer(void* arg) {
    while(1) {
        pthread_mutex_lock(&mtx);
        while(head == NULL) {
            pthread_cond_wait(&cond, &mtx);
        }
        int number = pop_head_node();//把移除节点的数字拿出来
        printf("number = %d\n", number);
        pthread_mutex_unlock(&mtx);
    }
}

int main(int argc, char* argv[])
{
    pthread_mutex_init(&mtx, NULL);
    pthread_cond_init(&cond, NULL);
 
    srand(time(NULL));
    pthread_t tid1;
    pthread_t tid2;
    pthread_create(&tid1, NULL, producer, NULL);
    pthread_create(&tid2, NULL, customer, NULL);
 
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值