Bootstrap

Linux-互斥锁与条件变量

互斥锁

        锁”是一个很普遍的需求,“为临界区上锁”实际描述的需求是:非临界区的任务可以同时执行,临界区中的任务只允许一个线程执行、不允许多个线程同时执行。

1. 定义互斥锁变量
    pthread_mutex_t mutex;
2. 初始化互斥锁变量
    //静态初始化--不用销毁
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    //动态初始化
    int pthread_mutex_init(pthread_mutex_t *restrict mutex,
 const pthread_mutexattr_t *restrict attr);
    功能:初始化一个互斥锁变量
    参数:
        mutex: 互斥锁变量的地址
        attr : 互斥锁变量的属性  NULL
    返回值:成功0  失败Errno

3. 上锁
        int pthread_mutex_lock(pthread_mutex_t *mutex);
    功能:锁定互斥锁变量--上锁   
    参数:
        mutex: 互斥锁变量的地址
       int pthread_mutex_trylock(pthread_mutex_t *mutex);
    功能:尝试锁定互斥锁变量--尝试上锁   
    参数:
        mutex: 互斥锁变量的地址   
    【当一个线程执行pthread_mutex_lock动作时,如果此时互斥锁变量被其他线程锁定,那么调用线程会阻塞直到该锁可用
    当一个线程执行pthread_mutex_trylock动作时,如果此时互斥锁变量被其他线程锁定,那么调用线程会直接返回】
    
4. 解锁
    int pthread_mutex_unlock(pthread_mutex_t *mutex);
    功能:释放互斥锁变量  
    参数:
        mutex: 互斥锁变量的地址

5. 销毁锁
    int pthread_mutex_destroy(pthread_mutex_t *mutex);
    只能对一个已经init初始化的互斥锁变量进行销毁

实例:

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

//定义互斥锁变量
pthread_mutex_t mutex;

int num = 150;        //临界资源

void *thread(void *arg){
    int id = *(int *)arg;
    for(int i=0;i<5;i++){
        //上锁
        pthread_mutex_lock(&mutex);
        num-=10;
        printf("%d : num = %d\n", id, num);
        //解锁
        pthread_mutex_unlock(&mutex);
    }

    pthread_exit(NULL);
}

int main(){
    pthread_t pt[3];
    int array[3] = {0};
    
    //初始化互斥锁变量
    pthread_mutex_init(&mutex, NULL);
    
    for(int i=0;i<3;i++){
        array[i] = i;    //不能对线程传参为可变的变量
        if(0 != pthread_create(pt+i, NULL, thread, (void *)&array[i])){
            perror("pthread_create");return -1;
        }
    }


    for(int i=0;i<3;i++){
        pthread_join(pt[i], NULL);
    }

    //销毁互斥锁变量
    pthread_mutex_destroy(&mutex);
    return 0;
}

条件变量

        与“互斥锁”的概念不同,条件变量是用来等待而不是用来上锁的,其本身不是锁。

        锁通常用于保护临界区,避免多个线程同时操作临界资源,而条件变量通常用于阻塞一个线程的运行,然后在合适的时机将其唤醒,例如:线程B的工作是处理数据,线程A的工作是获取数据,那么线程A在没有获取到有效数据之前线程B是不应当进行工作的,这时可以使用条件变量暂时将线程B阻塞,等待线程A获取到数据之后再将线程B唤醒运行。

        条件变量通常和互斥锁变量搭配使用,通常进行两个动作:

                (1)条件不满足,阻塞线程;

                (2)条件满足,解除线程的阻塞状态。

        互斥锁的主要目标是为了安排【互斥】任务

        条件变量的主要目标是为了安排【同步】任务

        【同步】:是指散布在不同任务之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。最基本的场景就是:两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。比如 A 任务的运行依赖于 B 任务产生的数据。

1. 定义条件变量
    pthread_cond_t cond;
2. 初始化
    //静态
    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    //动态
    int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
    功能:初始化
    参数:
        cond: 条件变量的地址
        attr: 属性 NULL
3. 判断条件是否满足(等待条件信号到达)-获取条件变量的资源--获取之前要先使用互斥锁,并且在函数内部会释放该互斥锁
    int pthread_cond_timedwait(pthread_cond_t *restrict cond,
 pthread_mutex_t *restrict mutex,
 const struct timespec *restrict abstime);
    int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
    功能:获取条件变量的资源(原子操作:不能被另一个线程或者进程打断)
        判断条件是否满足,如果满足,就解除阻塞上锁获取资源后移出队列
        如果不满足,就一直在队列休眠
    (1)上锁 为了放入等待队列
    (2)调用pthread_cond_wait函数 
        a. 进入队列
        b. 解锁,阻塞    [ 解锁解的是放入队列时争夺的资源锁 ]
        c. 等待条件满足
        d. 争夺锁变量    [ 争夺获取资源的锁变量,一旦获取资源就可以移出队列 ]   
        e. 执行动作,移出队列    
 4. 释放资源-唤醒队列中阻塞的线程
     // 发送条件信号 - 唤醒被该条件阻塞的一个线程
    int pthread_cond_signal(pthread_cond_t *cond);
    
    // 发送条件信号 - 唤醒所有被该条件阻塞的线程
    int pthread_cond_broadcast(pthread_cond_t *cond);          

5. 销毁条件变量
    int pthread_cond_destroy(pthread_cond_t *cond);

 生产者消费者模型:

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

//一个生产者和多个消费者模型

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond   = PTHREAD_COND_INITIALIZER;

int computer = 0;        //产品-临界资源

void *product(void *arg){
    int id = *(int *)arg;
    while(1){
        //上锁
        pthread_mutex_lock(&mutex);
        
        //生产产品 对临界资源操作
        computer++;
        printf("生产者 %d 生产1电脑\n", id);
        //唤醒一个线程
        pthread_cond_signal(&cond);
        //pthread_cond_broadcast(&cond);
        //解锁
        pthread_mutex_unlock(&mutex);
        
        sleep(1);        //生产耗时
    }
}

void *consumer(void *arg){
    int id = *(int *)arg;
    while(1){
        //上锁
        pthread_mutex_lock(&mutex);
       
        //等待条件满足 
        pthread_cond_wait(&cond, &mutex);
        //购买产品 对临界资源操作
        computer--;
        printf("消费者 %d 购买一台电脑  剩余电脑[%d]\n", id, computer);
        //解锁
        pthread_mutex_unlock(&mutex);
        
        sleep(1);        //消费耗时
    }
}

int main(){

#if 0
    pthread_t ptid;        //生产
    if(0 != pthread_create(&ptid, NULL, product, NULL)){
        perror("ptid");return -1;
    }
#endif

    pthread_t ptid[2];    //生产
    pthread_t ctid[5];    //消费
    int arr[5] = {0};

    //生产
    for(int i=0;i<2;i++){
        arr[i] = i+1;
        if(0 != pthread_create(&ptid[i], NULL, product, (void *)&arr[i])){
            perror("ptid");return -1;
        }
    }
    
    //消费
    for(int i=0;i<5;i++){
        arr[i] = i;
        if(0 != pthread_create(&ctid[i], NULL, consumer, (void *)&arr[i])){
            perror("ctid");return -1;
        }
    }
    
    //pthread_join(ptid, NULL);
    for(int i=0;i<2;i++){
        pthread_join(ptid[i], NULL);
    }
    for(int i=0;i<5;i++){
        pthread_join(ctid[i], NULL);
    }
    
    return 0;
}

;