【Linux】线程同步&条件变量

目录

1 线程同步的引入

2 条件变量&线程同步&竞争条件的概念

3 条件变量相关函数

初始化

销毁

等待条件满足

唤醒等待

4 demo代码——理解条件变量&线程同步

5 为什么 pthread_cond_wait 需要互斥量?

6 条件变量使用规范


1 线程同步的引入

例子生活化:

学校里有一间VIP自习室,只能让一个人在里面自习,为了保护使用者自习不被他人所打扰,配备以一把锁。张三是一个勤奋的学生,为了抢到VIP自习室的使用权,凌晨六点就已经到达了自习室开始了自习,并将自习室给锁上了。后面陆续过来的人就只能在自习室外面等待。某个时刻张三想要上厕所,为了不让别人在此期间进入自习室,出门的时候反手就把门给锁上了,所以在张三上厕所的期间,张三自习室里面自己的学习资料就可得到很好的保护起来。第一个阶段:多线程访问临界资源的加锁保护,做到了互斥张三上完厕所回来了,继续之前的自习活动。过了一会,张三由于没有吃早饭,肚子开始饿了起来,打算结束自习状态去吃东西。所以张三离开自习室,刚刚放下钥匙的时候,心里又想着吃完饭回来就可能要等待自习室的了,觉得不好,所以想要回去自习。由于钥匙和张三此时离的很近,之前等待钥匙的人离钥匙较远,所以此时竞争钥匙的时候,张三的竞争力大,又重新拿到了钥匙。所以其他人只能继续等待。接着,张三用钥匙开启了VIP自习室的门,刚把门锁上,张三的肚子又饿了起来,又重复了之前的动作。由于食物的资源在未来的一段时间里面,都无法到达,所以张三就一直在重复着锁与解锁的动作,没有进入学习的状态,而别人也没有得到锁的机会,从而也无法学习。第二阶段:多线程按照规则锁与解锁,没错,但是不合理)后来,校方发现了VIP自习室的这个不合理的地方,就规定了:自习完毕的人,归还完钥匙之后,不能立即申请钥匙锁,而必须重新排队,在外面人也要进行等待排队。——在安全的规则下,多线程访问资源具有一定的顺序性,为了合理解决饥饿问题;提出了线程同步,让多线程协同工作!

【Linux】线程同步&条件变量_第1张图片

2 条件变量&线程同步&竞争条件的概念

  • 条件变量
    • 当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
    • 例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。
  • 同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步
  • 竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也不难理解

3 条件变量相关函数

初始化

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict
attr);
        参数:
                 cond:要初始化的条件变量
                 attr:NULL(条件变脸的属性)

销毁

int pthread_cond_destroy(pthread_cond_t *cond)

等待条件满足

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
        参数:
                cond:要在这个条件变量上等待
                mutex:互斥量

唤醒等待

int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒条件队列中所有的线程
int pthread_cond_signal(pthread_cond_t *cond);//唤醒条件队列中的一个线程

4 demo代码——理解条件变量&线程同步

【解锁操作可以在任意一个线程中进行】

pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
void* PthreadRoutine(void* args)
{
    const char* name=static_cast(args);
    while(true)
    {
        pthread_mutex_lock(&mutex);
        cout<

【条件变量的使用】

#include 

using namespace std;

#include 
#include 

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

void *PthreadRoutine(void *args)
{
    const char *name = static_cast(args);
    while (true)
    {
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond, &mutex); // 挂起等待,等待某个条件满足,等待其它线程唤醒该线程
        cout << name << " run" << endl;
        pthread_mutex_unlock(&mutex);
    }
}
int main()
{
    pthread_t t[4];
    for (int i = 0; i < 4; ++i)
    {
        char *name = new char[64];
        snprintf(name, 64, "thread-%d", i + 1);
        pthread_create(t + i, nullptr, PthreadRoutine, name);
    }
    sleep(3);

    while (true)
    {
        pthread_cond_signal(&cond); // 唤醒一个线程
        sleep(1);
    }

    for (int i = 0; i < 4; ++i)
    {
        pthread_join(t[i], nullptr);
    }

    return 0;
}

【Linux】线程同步&条件变量_第2张图片

5 为什么 pthread_cond_wait 需要互斥量?

  • 条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程。
  • 条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据。 

【Linux】线程同步&条件变量_第3张图片

  • 按照上面的说法,我们设计出如下的代码:先上锁,发现条件不满足,解锁,然后等待在条件变量上不就行了,如下代码: 

// 错误的设计
pthread_mutex_lock(&mutex);
while (condition_is_false)

{
        pthread_mutex_unlock(&mutex);
        //解锁之后,等待之前,条件可能已经满足,信号已经发出,但是该信号可能被错过
        pthread_cond_wait(&cond);
        pthread_mutex_lock(&mutex);
}
pthread_mutex_unlock(&mutex);

  • 由于解锁和等待不是原子操作。调用解锁之后, pthread_cond_wait 之前,如果已经有其他线程获取到互斥量,摒弃条件满足,发送了信号,那么 pthread_cond_wait 将错过这个信号,可能会导致线程永远阻塞在这个 pthread_cond_wait 。所以解锁和等待必须是一个原子操作。
  • int pthread_cond_wait(pthread_cond_ t *cond,pthread_mutex_ t * mutex); 进入该函数后,会去看条件量等于0不?等于,就把互斥量变成1,直到cond_ wait返回,把条件量改成1,把互斥量恢复成原样。

6 条件变量使用规范

等待条件代码

pthread_mutex_lock(&mutex);
while (条件为假)//细节:解除等待的时候,判断条件是否满足,确保解除等待后任何时候条件都是满足的
        pthread_cond_wait(cond, mutex);
修改条件
pthread_mutex_unlock(&mutex);

给条件发送信号代码

pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);

你可能感兴趣的:(Linux,java,开发语言,linux,c++)