多线程编程总结(二)——条件变量和互斥锁(转载)

转载原文:https://blog.csdn.net/skyroben/article/details/72850109

互斥锁

1.什么叫互斥锁?

​ 互斥锁(也成互斥量)可以用于保护关键代码段,以确保其独占式的访问,类似于二元信号量。二者都可以称为挂起等待锁———锁资源得不到满足,就会被挂起,在信号量或互斥锁上等待。

注:当前线程的PCB在互斥量的等待队列等待以便快速唤醒。进程等待的本质是将PCB列入某个队列等待。

2.使用方式

​ 当进入关键代码段时,需要申请互斥锁,如果失败就挂起等待,将PCB列入互斥锁的等待队列,成功就将其加锁,等价于二元信号量的P操作;离开关键代码时,对互斥锁进行解锁,以便唤醒其他等待该互斥锁的线程,这等价二元信号量的V操作。

3.互斥锁相关的五个函数

#include <pthread.h> 
//初始化
int pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutexattr_t* mutexattr); 
//销毁
int pthread_mutex_destroy(pthread_mutex_t * mutex); 
//加锁阻塞版
int pthread_mutex_lock(pthread_mutex_t * mutex); 
//加锁非阻塞版
int pthread_mutex_trylock(pthread_mutex_t * mutex); 
//解锁
int pthread_mutex_unlock(pthread_mutex_t * mutex);

参数解析:

  1. mutex指向操作的目标锁,互斥锁的类型是pthread_mutex_t的结构体;
  2. 初始化时mutexattr参数指定互斥锁的属性。一般设置为NULL取它的默认属性。

  3. 如果mutex变量是静态分配的(全局变量或static变量),则可以使用宏定义PTHREAD_MUTEX_INITIALIZER初始化一个互斥锁。宏定义实际上是把互斥锁的每个字段都初始化为0。
  4. 销毁互斥锁是为了释放其占用的内核资源,不能销毁一个已经加锁的互斥锁。

  5. pthread_mutex_lock函数以原子操作给一个互斥锁加锁。一个线程可以调用pthread_mutex_lock获得Mutex,如果这时另一个线程已经用pthread_mutex_lock获得了该Mutex,则当前线程需要挂起等待,直到另一个线程调用 pthread_mutex_unlock释放Mutex,当前线程被唤醒,才能获得该Mutex并继续执行。

  6. 如果一个线程既想获得锁,又不想挂起等待,可以调用pthread_mutex_trylock,如果Mutex已经被 另一个线程获得,这个函数会失败返回EBUSY,而不会使线程挂起等待。

  7. 上面的函数成功时返回0,失败时返回错误码。

4.互斥锁产生的问题——死锁

死锁:集合中的每一个进程都在等待只能由本集合中的其他进程才能引发的事件,那么该组进程是死锁的。

线程死锁情形:

img

死锁产生的四个必要条件(缺一不可):

(1)非抢占。不能强行抢占进程中已有的资源。

(2)互斥。一次只能有一个进程使用资源。其他进程不能访问已经分配给其他进程的资源。

(3)占有且等待。当一个进程在等待分配其他资源时,其继续占有已分配得到的资源

(4)循环等待。存在一个封闭的进程链,使得每个资源至少占有此链中下一个进程所需的资源。

解决死锁的方法:

(1)预防死锁。即破坏死锁产生的四个条件。

(2)避免死锁。保证按相同顺序获得锁。(如果一个进程的请求会导致死锁,则不启动该进程;如果一个进程增加资源的请求会导致死锁,则不允许分配)

条件变量

1.什么叫条件变量?

互斥锁用于同步线程对共享数据的访问的话,那么条件变量则用于在线程之间同步共享数据的值。

条件变量提供一种机制:当某个数据达到某个值的时候,唤醒等待该共享数据的资源。这样就能保证顺序访问

一般与互斥锁一起使用:

(1)因为互斥只能保持访问不出错。(但是由于优先级或其他特性可能导致其他线程得不到资源)

(2)同步保证访问不出错的同时,一般还让多个线程或执行流协同(访问临界资源具有顺序性)

2.与条件变量相关的五个函数

#include <pthread.h> int pthread_cond_init(pthread_cond_t* cond, const pthread_condattr_t* cond_attr); int pthread_cond_destroy(pthread_cond_t* cond); int pthread_cond_broadcast(pthread_cond_t* cond); int pthread_cond_signal(pthread_cond_t* cond); int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);

(1)第一个参数与互斥锁类似,cond指向要操作的目标条件变量,条件变量的类型是pthread_cond_t结构体

(2)cond_attr参数指定条件变量的属性如果设置为NULL采用默认属性。和互斥锁类似,也可以采用

​ PTHREAD_COND_INITIALIZER初始化静态分配的cond变量

(3)一个Condition Variable(条件变量)总是和一个Mutex搭配使用的。一个线程可以调用pthread_cond_wait在这个条件变量上阻塞等待,这个函数做以下操作

​ a: 释放Mutex b: 阻塞等待 c: 当被唤醒时,重新获得Mutex并返回

(4)以上函数成功返回0,失败返回错误码

3.用条件变量和互斥锁完成同步与互斥机制

模型:

​ 生产者生产一个结构体串在链表的表头上,消费者 从表头取走结构体。

#include <stdio.h>
#include <pthread.h>
#include <error.h>
#include <assert.h>
#include <stdlib.h>
typedef  int DataType;
typedef struct listNode{
    struct listNode* _next;
    DataType _data;
}node, *pnode, *plist;
pnode listHead = NULL;
//定义成全局,初始化简单
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t  need_product = PTHREAD_COND_INITIALIZER;
pnode CreateNode(DataType data){
    pnode newnode = (pnode)malloc(sizeof(node));
    newnode->_data = data;  newnode->_next = NULL;
    return newnode;
}
void Init(plist *list){
    if (NULL != list)
    {       
        *list = NULL;
    }
}
void PushFront(plist *list, DataType d){
    assert(list != NULL);
    pnode newnode = CreateNode(d);
    pnode cur = *list;
    if (cur == NULL){
        *list = newnode;
        newnode->_next = NULL;
    }   
    else{   
        *list = newnode;
        newnode->_next = cur;
    }
    return;
}
void PopFront(plist *list){
    assert(list != NULL);
    pnode cur = *list;
    if (cur == NULL){   
        return; 
    }   
    else if (cur->_next == NULL){
        cur = cur->_next;
        free(cur);  
        *list = NULL;
    }
    else{
        *list = cur->_next;
        free(cur);
        cur = NULL;
    }   
    return;
}
void Destroy(plist  *list){
    pnode cur = *list;
    while (cur != NULL) {
        *list = cur->_next;
        cur = *list;
    }
    return;
}
void ShowList(plist list){
    pnode cur = list;
    while (cur != NULL) {
        printf("%d ", cur->_data);
        cur = cur->_next;   
    }   
    printf("\n");
}  
void *product(void * _val){ 
    while (1){  
        sleep(1);
        //加锁
        pthread_mutex_lock(&lock);
        int num = rand() % 100;
        Init(&listHead);
        PushFront(&listHead, num);
        printf("call consum:product success and the value is %d\n", num);
        //解锁
        pthread_mutex_unlock(&lock);
        //唤醒等待目标变量的线程
        pthread_cond_signal(&need_product);
    }
}
void *consum(void *_val){
    while (1){
        pthread_mutex_lock(&lock);
        while (listHead == NULL){
            //等待目标条件变量,锁使得pthread_cond_wait操作的原子性
            pthread_cond_wait(&need_product, &lock);
        }
        printf("call product:consum success and the value is %d\n", listHead->_data);
        PopFront(&listHead);
        ShowList(listHead);
        pthread_mutex_unlock(&lock);
    }
    return NULL;
} 
int main(){
    pthread_t t_product;
    pthread_t t_consum;
    pthread_create(&t_product, NULL, product, NULL);
    pthread_create(&t_consum, NULL, consum, NULL);
    //回收线程
    pthread_join(t_product, NULL);
    pthread_join(t_consum, NULL);
    return 0;
}

运行结果:

img

实验结果分析:

如果不采用互斥锁使得操作的原子性,有可能造成数据的二义性;不使用条件变量使得操作按照一定顺序执行,可能导致生产者一直生产或者消费者一直消费,可能导致程序效率低下等问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个使用互斥条件变量实现线程间通信的示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <pthread.h> #define BUFFER_SIZE 10 int buffer[BUFFER_SIZE]; // 共享缓冲区 int count = 0; // 缓冲区中元素个数 pthread_mutex_t mutex; // 互斥 pthread_cond_t cond; // 条件变量 void *producer(void *arg) { int item = 0; while (1) { pthread_mutex_lock(&mutex); // 加 if (count == BUFFER_SIZE) { // 缓冲区已满,等待消费者消费 pthread_cond_wait(&cond, &mutex); } buffer[count++] = item++; // 生产一个物品,放入缓冲区 printf("Producer produces item %d\n", item); pthread_mutex_unlock(&mutex); // 解 pthread_cond_signal(&cond); // 唤醒一个等待的消费者 } pthread_exit(NULL); } void *consumer(void *arg) { int item = 0; while (1) { pthread_mutex_lock(&mutex); // 加 if (count == 0) { // 缓冲区为空,等待生产者生产 pthread_cond_wait(&cond, &mutex); } item = buffer[--count]; // 消费一个物品,从缓冲区中取出 printf("Consumer consumes item %d\n", item); pthread_mutex_unlock(&mutex); // 解 pthread_cond_signal(&cond); // 唤醒一个等待的生产者 } pthread_exit(NULL); } int main() { pthread_t producer_tid, consumer_tid; // 初始化互斥条件变量 pthread_mutex_init(&mutex, NULL); pthread_cond_init(&cond, NULL); // 创建生产者和消费者线程 pthread_create(&producer_tid, NULL, producer, NULL); pthread_create(&consumer_tid, NULL, consumer, NULL); // 等待线程结束 pthread_join(producer_tid, NULL); pthread_join(consumer_tid, NULL); // 销毁互斥条件变量 pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond); return 0; } ``` 在上面的示例代码中,我们使用了一个缓冲区来实现生产者和消费者之间的通信。生产者将生产的物品放入缓冲区,消费者从缓冲区中取出物品进行消费。当缓冲区已满时,生产者会等待消费者消费;当缓冲区为空时,消费者会等待生产者生产。在等待的过程中,使用条件变量来进行线程的阻塞和唤醒。当生产者放入一个物品时,会唤醒一个等待的消费者;当消费者取出一个物品时,会唤醒一个等待的生产者。同时,使用互斥来保护共享缓冲区的访问,确保线程安全。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值