关于多线程中的条件变量和虚假唤醒学习笔记

一、互斥量

  互斥量又叫互斥锁,它是用来确保某一时刻一些数据(比如链表)只会被一个线程访问。

有两种初始化的方式:

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

或者

pthread_mutex_t lock;

pthread_mutex_init(&lock, NULL);

然后就是使用,加锁和解锁:

加锁需要使用pthread_mutex_lock(&lock);(pthread_mutex_trylock(&lock)会尝试加锁,区别是,如果互斥量已经被加锁,使用trylock不会阻塞,而是直接返回EBUSY)

解锁需要用到pthread_mutex_unlock(&lock);

二、条件变量

  条件变量也是多线程之间共享数据时用到的。它适用的情况是,线程1改变了数据,达到某一个程度后,线程2才能进行(例如生产者-消费者模式中,只有生产者生产了东西,消费者才能消费)。但是如果让线程2一直阻塞在那边,不断地检查是否它自身能否进行(在生产者-消费者模式中,条件可能是东西是否被生产出来了),很浪费资源。最好的就是,线程2先休眠在那边,不用一直查询条件是否满足,等到线程1这边修改了数据,达到了线程2进行的条件时,通知线程2就可以了。条件变量就是起了这个作用。

类似互斥量,条件变量也有两种初始化方式:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

或者

pthread_cond_t cond;

pthread_cond_init(&cond);

然后就是使用,这里用到的函数是pthread_cond_wait(&cond, &lock);(还有一个可以设置timeout的类似函数)

我一共写了两个代码进行研究,先来第一个:

 

#include
#include
#include
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int n = 0;
void *wait(void *p)
{
    printf("step 1 The start of wait thread\n");
    pthread_mutex_lock(&mutex);
    while(2 != n)
    {
        printf("step 2 the condition is false, the thread will sleep");
        pthread_cond_wait(&cond, &mutex);
        printf("step 3 now n = %d\n", n);
    }
    pthread_mutex_unlock(&mutex);
    return (void *)0;
}
void *signal(void *p)
{
    printf("step 1 The start of signal thread\n");

    pthread_mutex_lock(&mutex);
    sleep(3);
    n = 2;
    pthread_mutex_unlock(&mutex);
    pthread_cond_signal(&cond);
    return (void *)0; 
}

int main()
{
    pthread id1, id2;
    ret = pthread_create(&id1, NULL, wait, NULL);
    if(ret)
    {
        printf("Create fail\n");
        return 0;
    }
    

    ret = pthread_create(&id2, NULL, signal, NULL);
    if(ret)
    {
        printf("Create fail\n");
        return 0;
    }
    pthread_join(id1, NULL);
    pthread_join(id2, NULL);
    printf("Main thread finished\n");
    return 0;
}

 

 

这是一个最简单的wait-signal模型。

 

 

wait和signal两个线程里都有对互斥量进行加锁的操作。如果signal先加锁,那么wait进行到step 1就阻塞住了,等signal解锁之后,wait才继续往下执行;如果wait先加锁,那么signal也同样会阻塞。这是互斥量的作用,互斥量加锁阻塞的是一段代码。

但是第二种情况下,wait里面有一个pthread_cond_wait函数,这个函数做了什么事儿呢?

它先把wait进程休眠(而不是阻塞在那边,不用占用CPU资源),然后解锁当前mutex(使得signal进程可以使用mutex),这两步是原子操作。之后wait进程便进入了等待状态,受到signal之后,便又对mutex加锁,函数返回。

这里有几个问题:

1.signal进程发送信号和mutex解锁的先后问题。如果发送signal在前,那么wait唤醒之后要对mutex进行加锁,但是如果此时signal并没有对mutex完成解锁,那么wait又会进入休眠。如果是对mutex解锁在前,那么wait线程收到signal似乎就能正确的对mutex进行加锁了。我实验了发现这两者并没有区别,所以这边还是存疑的。

2.wait线程中判断条件用while而不是if.这边我纠结了很久,因为存在虚假唤醒---即使没有线程调用发送signal或者broadcast,原先调用pthread_cond_wait的函数也有可能返回,这就有点坑爹了。如果哪一次pthread_cond_wait返回时不时因为我主动的发signal,那不就是在判断条件为假的情况下也会执行接下来的代码吗?所以用while来判断,这样的话即使是虚假唤醒,while又会回去检查一下条件是否为真。如果是if, 就会直接往下执行了。while可以确保pthread_cond_wait之前和之后(唤醒之后)条件都是我需要的。

3.进程被唤醒之后,是从pthread_cond_wait接着往下执行。

4.pthread_join函数,可以把主线程阻塞在那边,等子线程都执行完才会执行主线程。如果不加这个,子线程还没有执行完,主线程就退出了。

我另一个看的代码是迅雷的面试题,开启三个线程,使得这三个线程交替打印ABCABCABC,感兴趣的可以去搜来看看。

终于写完了,这是我的第一篇博客,主要是做个笔记,为了以后复习方便,也为了面试做准备。以后还会继续写。

看到这边的人应该能感觉到,我是一个技术小白,希望大家有什么意见和建议都能告诉我,欢迎讨论。

你可能感兴趣的:(关于多线程中的条件变量和虚假唤醒学习笔记)