线程同步——条件变量

目录

线程同步

死锁的概念

死锁是什么?

死锁的四个必要条件

避免死锁

同步的概念与竞态条件

理解同步

概念

条件变量

条件变量的初始化

对条件变量的操作

使用同步控制多线程执行任务


线程同步

死锁的概念

死锁是什么?

前面我们知道了,如果访问一个全局数据,或者静态的数据,我们往往是需要加锁的,那么如果这种资源没了呢?

死锁往往就是各个执行流占有一定资源且不会释放,并且还再申请其他执行流锁持有的资源,此时就会处于一种永久等待的状态。

这种情况下,所以进程的执行不会被推进,而这就叫死锁。

死锁的四个必要条件

那么如果想要形成死锁是需要几个条件的:

  1. 互斥条件:一个资源每次只能被一个执行流执行

  2. 请求与保持:一个执行流请求资源时被阻塞,对已获得的资源保持不放

  3. 不剥夺:一个执行流在已获得资源,在未使用完之前,对资源不放

  4. 循环等待:若干执行流之间形成一i中头尾相接的循环等待资源关系

什么是必要条件呢?

也就是说,只要形成了死锁,那么就一定会有这四个条件的,所以只需要我们破坏掉一个或者多个条件,那么就可以避免死锁。

避免死锁

那么如何避免死锁呢?

第一个互斥条件:互斥条件是为了保证临界资源访问的正确性,所以如果普坏掉互斥条件的话,同时代码也就是有问题的,所以互斥条件并不能被破坏。

第二个请求与保持:这个条件和不剥夺其实是优点类似的,所以其实可以在让如果资源没有满足的话,那么就释放掉自己的资源,如果这样的话,那么就可以破坏掉死锁,而也可以让线程剥夺资源,这样也可以避免死锁。

第三个循环等待:假设一个线程在等待已经有了A资源,临一个线程竞争到了B资源,那么第一个线程此时需要B资源,但是第二个线程此时需要A资源,那么此时就形成了循环等待,所以为了避免死锁,可以让资源的请求顺序一致。

同步的概念与竞态条件

理解同步

在说同步之前,我们先理解一下什么是同步!

现在你是一个学生,你们现在下课了,然后都跑去食堂吃饭。

此时由于打饭的窗口比较少,所以只能很多人排队到一个窗口,由于打饭的阿姨只有一个,所以每一次只能给一个人打饭。

此时你在排队的时候,你前面有一个又高又壮的男生,此时到他打饭了,然后他打完饭之后,瞬间就吃完了,然后他还是在打饭,就这样,他刚打完饭就吃掉,然后食堂阿姨又给打,就这样一直下去。

因为他排队了,并没有随便插队,而且打饭的阿姨也只有,阿姨只关心打饭,所以阿姨不管是谁,只要排队到这里了,那么他就给打饭,所以此时你和你后面的同学都吃不到饭。

那么这个又高又壮的男生错了吗?没有错,因为人家也是排队了。

但是合理吗?不合理!

因为这样做的话,那么就会让你和你后面的同学有饥饿问题。

那么应该怎么样呢?应该让打完饭的同学,如果还想继续打饭,那么就应该重新拍到队尾去,此时让你们都是按照某种特定的顺序去打饭。

概念

同步:同步就是在保证数据安全的前提下,让线程按照某种特定的顺序访问临界资源,从而有效的避免饥饿问题,就叫做同步。

竞态条件:因为系统调用执行流的时序问题,从而导致程序异常,这就叫做竞态条件。

上面的概念知识一个简单的理解,下面先看代码来理解一下:

条件变量

条件变量就是为了同步,所以下面看一下条件变量,其实条件变量和mutex基本一样,接口也是很相同的。

那么我们在说一下为什么需要条件变量,我们前面说了一个关于同步的理解,那么下面说一下关于条件变量的理解同时对同步继续理解一下。

假设你现在去买手机,这部手机是卖的很好,现在你先想要去店里买手机了。

此时你到了店里,你问店员说,xxx手机多少钱,你想要买,但是店员告诉你说很不好意思,这部手机现在卖完了,目前还没有到货,所以你只能回去了。

那么到了第二天,此时你又去了那家店里,你问店员说那不手机到了没有,此时店员还是回答没有,然后由于你害怕手机到货后自己买不到,所以你每天都去店里问一下,你每天都去问,店员看着你都觉得烦的很。

那么此时你做错了吗?没有做错!

那么合理吗?不合理!

为什么?因为你这样做,不仅在浪费自己的时间,同时也是在浪费店员的时间,也就是你在浪费资源。

那么应该怎么做呢,你应该让店员等手机到货后通知你一声,这样你就不用每天都跑过去浪费时间了。

而此时店员告诉你这个过程也叫做同步。

而手机到没到,此时这就是你在手机这个条件下等待,如果手机没到的话,那么你就不用忙活了,你在家等就好了,如果手机到了,那么店员告诉你。

上面的这个就是条件变量和同步。

条件变量的初始化
NAME
       pthread_cond_destroy, pthread_cond_init - destroy and initialize condition variables
​
SYNOPSIS
       #include <pthread.h>
​
       int pthread_cond_destroy(pthread_cond_t *cond);
       int pthread_cond_init(pthread_cond_t *restrict cond,
              const pthread_condattr_t *restrict attr);
       pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
  • pthread_cond_init:是关于条件变量的初始化的,和 pthread_mutex_init 基本一样,只需要传入 pthread_cond_t 的变量即可。

  • 如果 pthread_cond_t 的变量是全局的,那么也可以使用 PTHREAD_COND_INITIALIZER 的宏来初始化。

  • pthread_cond_init 的第二个参数也是和mutex 的初始化函数相同,也是设置条件变量的属性的,一般我们设置为 nullptr 即可。

  • 如果是使用的局部变量,那么就需要使用 pthread_cond_init 来初始化,但是使用这个函数初始化后,那么就需要使用 pthread_cond_destroy 函数来释放。

对条件变量的操作

还有两个函数是用来对条件变量操作的,和 mutex 基本相同,mutex 的操作有个 lock 和 unlock 操作,那么条件变量是用来控制同步的,那么当然需要控制线程的启动与终止,所以就有两个 wait 和 signal ,意思就是在对应的条件变量下面等待,还有就是唤醒等待的线程。

NAME
       pthread_cond_timedwait, pthread_cond_wait - wait on a condition
​
SYNOPSIS
       #include <pthread.h>
​
       int pthread_cond_wait(pthread_cond_t *restrict cond,
              pthread_mutex_t *restrict mutex);
  • 这个函数就是在某个条件变量下等待,如果这个条件不成立,那么就阻塞。

  • 第一个参数就是在哪一个条件变量下等待,传入一个 pthread_cond_t 的指针。

  • 第二个参数就是一个锁的指针。

NAME
       pthread_cond_broadcast, pthread_cond_signal - broadcast or signal a condition
​
SYNOPSIS
       #include <pthread.h>
​
       int pthread_cond_broadcast(pthread_cond_t *cond);
       int pthread_cond_signal(pthread_cond_t *cond);
  • pthread_cond_signal 该函数是唤醒某个条件变量下的一个线程。

  • pthread_cond_broadcast 该函数是唤醒所有的在某个条件变量下等待的线程。

  • 其中这两个函数的参数都是某个条件变量。

使用同步控制多线程执行任务

下面我们使用一份代码来看一下同步如何使用:

先说一下我们下面想写的代码逻辑:

  1. 我们想创建一批线程,然后让这些线程执行自己的任务。

  2. 为了控制线程的同步,我们使用 mutex 和 cond 来控制。

  3. 所以我们为了能让这些线程在同一把锁和同一个条件变量的条件下,我们需要将锁与条件变量传给线程。

  4. 所以我们需要一个结构体/类来将这些属性抽象出来,然后定义对象传给线程。

  5. 我们需要记录这些线程的id,方便后面join

为了方便控制线程,我们还可以使用一个全局的数据来控制:

// 宏的定义和全局的数据
#define PTHREAD_NUM 5
volatile int quit = 0;

线程要执行的任务

void *threadRun1(void *args)
{
    threadDate *date = reinterpret_cast<threadDate *>(args);
    while (true)
    {
        pthread_mutex_lock(date->_mutex);
        // cout << "isquit: " << !quit << endl;
        if (!quit)// 访问临界资源也是计算,需要加锁
        {
            // 等待需要在       条件变量          锁
            pthread_cond_wait(date->_cond, date->_mutex);
            if(!quit) cout << date->_name << "[ "<< pthread_self() << "] "<< "执行下载任务 Download...." << endl;
            pthread_mutex_unlock(date->_mutex);
        }
        else
        {
            cout << "线程 " << pthread_self() << "退出" << endl;
            pthread_mutex_unlock(date->_mutex);
            break;
        }
    }
    delete date;
}
​
void *threadRun2(void *args)
{    
    threadDate *date = reinterpret_cast<threadDate *>(args);
    while (true)
    {
        pthread_mutex_lock(date->_mutex);
        // cout << "isquit: " << !quit << endl;
        if (!quit)
        {
            pthread_cond_wait(date->_cond, date->_mutex);
            if(!quit) cout << date->_name << "[ "<< pthread_self() << "] "  << "执行播放任务 Play...." << endl;
            pthread_mutex_unlock(date->_mutex);
        }
        else
        {
            cout << "线程 " << pthread_self() << "退出" << endl;
            pthread_mutex_unlock(date->_mutex);
            break;
        }
    }
    delete date;
}
​
void *threadRun3(void *args)
{
    threadDate *date = reinterpret_cast<threadDate *>(args);
    while (true)
    {
        pthread_mutex_lock(date->_mutex);
        // cout << "isquit: " << !quit << endl;
        if (!quit)
        {
            pthread_cond_wait(date->_cond, date->_mutex);
            if(!quit) cout << date->_name << "[ "<< pthread_self() << "] " << "执行上传任务 Uploading...." << endl;
            pthread_mutex_unlock(date->_mutex);
        }
        else
        {
            cout << "线程 " << pthread_self() << "退出" << endl;
            pthread_mutex_unlock(date->_mutex);
            break;
        }
    }
    delete date;
}
​
void *threadRun4(void *args)
{
    threadDate *date = reinterpret_cast<threadDate *>(args);
    while (true)
    {
        pthread_mutex_lock(date->_mutex);
        // cout << "isquit: " << !quit << endl;
        if (!quit)
        {
            pthread_cond_wait(date->_cond, date->_mutex);
            if(!quit) cout << date->_name << "[ "<< pthread_self() << "] " << "执行存储任务 Storage...." << endl;
            pthread_mutex_unlock(date->_mutex);
        }
        else
        {
            cout << "线程 " << pthread_self() << "退出" << endl;
            pthread_mutex_unlock(date->_mutex);
            break;
        }
    }
    delete date;
}
​
void *threadRun5(void *args)
{
    threadDate *date = reinterpret_cast<threadDate *>(args);
    while (true)
    {
        pthread_mutex_lock(date->_mutex);
        // cout << "isquit: " << !quit << endl;
        if (!quit)
        {
            pthread_cond_wait(date->_cond, date->_mutex);
            if(!quit) cout << date->_name << "[ "<< pthread_self() << "] " << "执行发生任务 Send...." << endl;
            pthread_mutex_unlock(date->_mutex);
        }
        else
        {
            cout << "线程 " << pthread_self() << "退出" << endl;
            pthread_mutex_unlock(date->_mutex);
            break;
        }
    }
    delete date;
}

传给线程的结构体

// 线程的数据
struct threadDate
{
public:
    threadDate(pthread_mutex_t *mutex, pthread_cond_t *cond, string name)
        : _mutex(mutex), _cond(cond), _name(name)
    {
    }
​
public:
    pthread_mutex_t *_mutex;// 为了让临界资源的访问安全,需要的锁
    pthread_cond_t *_cond; // 控制线程同步的条件变量
    string _name; // 线程名
};

主线程执行的函数

void test1()
{
    // 定义锁和条件变量
    pthread_mutex_t mutex;
    pthread_cond_t cond;
    // 初始化锁和条件变量
    pthread_mutex_init(&mutex, nullptr);
    pthread_cond_init(&cond, nullptr);
​
    // 函数指针 类型为返回值 void* 参数为 void*
    typedef void *(*func)(void *);
    // 为每个线程做不同的任务
    vector<func> funcs{threadRun1, threadRun2, threadRun3, threadRun4, threadRun5};
    // 用来存储线程 ID,方便后面 join
    vector<pthread_t> pthreadID;
    pthread_t tid;
    // 创建一批线程
    for (int i = 0; i < PTHREAD_NUM; ++i)
    {
        string name = {"thread "};
        name += to_string(i + 1);
        // 将线程的数据构建一个对象,然后传给线程
        threadDate *date = new threadDate(&mutex, &cond, name);// 这个对象传给了线程,所以线程使用完之后是需要释放的
​
        pthread_create(&tid, nullptr, funcs[i], date);
        pthreadID.push_back(tid);
        // cout << "创建线程 " << tid << " 成功" << endl;
    }
​
    sleep(1);
    // 主线程一次唤醒一个线程
    for(int i = 1; ; ++i)
    {
        cout << "main thread[ " << pthread_self() << " ]第 " << i << " 次唤醒线程" << endl;
        // pthread_cond_signal(&cond);
        pthread_cond_broadcast(&cond);
        sleep(1);
        if(i == 5)
        {
            quit = 1;
            pthread_cond_broadcast(&cond);
            break;
        }
    }
​
​
    // 等待线程
    for (int i = 0; i < PTHREAD_NUM; ++i)
    {
        pthread_join(pthreadID[i], nullptr);
        cout << "等待线程 " << pthreadID[i] << "成功" << endl;
    }
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Naxx Crazy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值