生产者消费者模型

知识回顾

存在两个线程,线程tha,thc,线程a输出0 ~ 100的偶数,线程c输出0 ~ 100的奇数,代码如下,我们会发现在进入等待的时候我们会不断循环,而我们学过lambda表达式,这里也可以使用。

const int n = 100;
bool tag = true;
int i = 0;
std::mutex mtx;
std::condition_variable cv;
void funa() {
    std::unique_lock<mutex> lock(mtx);
    for (; i <= n; ++i) {
        /*
        while (i % 2 != 0 && i <= n) {
            cv.wait(lock);
        }*/
        cv.wait(lock,[&]()->bool {return !( i % 2 != 0 && i <= n);})
        if (i <= n) 
        cout << "funa :  " << i << endl;
        cv.notify_all();
    }
}
void func() {
    std::unique_lock<mutex> lock(mtx);
    for (; i <= n; ++i) {
		/*
        while (i % 2 == 0 && i <= n) {
            cv.wait(lock);
        }
        */
        cv.wait(lock,[&]()->bool { return !(i % 2 == 0 && i <= n);})
        if (i <= n) 
        cout << "func : " << i << endl;
        cv.notify_all();
    }
}

int main() {
    std::thread tha(funa);
    std::thread thc(func);

    tha.join();
    thc.join();

    return 0;
}

用了lambda表达式怎么理解呢?
在这里插入图片描述
上图是函数的参数和实现,其底层的实现和我们上面代码一样就是循环。注意循环条件取反。
wait函数的运行是这样的,首先调用lambda表达式,表达式为真,不执行wait的等待,如果为假,进入等待,唤醒之后仍然执行lambda表达式来判断是否执行wait等待。我们称这里的lambda表达为谓词表达,其返回值为bool类型,可以通过返回值来判断语句是否执行。

生产者消费者模型

概念

什么是生产者消费者模型呢?
生产者消费者相信大家都学过,而这个模型也和名字一样,生产者生产数据,消费者消费数据,这里存在一个队列,生产者生产的数据存放在队列中,消费者消费数据从队列中消费,而容器的大小是固定的,当容器中数据个数大小到达上限时,生产者不能生产,如果容器中没数据,消费者不能消费。
而为什么会存在一个容器呢?试想如果生产者和消费者直接进行数据接收传输,如果生产者消费者中任何一方存在问题,这就导致了另一方页会出现问题。而用了容器,就不会出现这样的问题,而且如果一方效率很低时,另一方可以干其他的事情。
这个模型在工作中是很常用的模型,比如我们的cs结构,客户端和服务器进行数据传输的时候,实质上是发送方将数据放入到数据缓冲区中,接收方从缓冲区中读取数据。这也是生产者消费者模型。

例一

我们可以改一下上面的代码,让线程a生产数据,线程c消费数据,可以试一下做一个见到那的消费者生产者模型。为了提高代码的可用性,所以再设置三个线程,两个消费者,一个生产者。

const int n = 100;
int mydata; // 数据值,也就是容器,容器大小为1
bool prod = false; // 容器是否满了
bool stop = false;
bool CS = false;//判断数据是否被其他消费者消费
std::mutex  mtx;
std::condition_variable cv;

void Producer()
{
    std::unique_lock<std::mutex> lock(mtx);
    for (int i = 0; i <= n; ++i)
    {
        while (prod)
        {
            cout << "Produrer cv.wait()" << endl;
            cv.wait(lock);
        }
        mydata = i;
        printf("Producer data: %d \n", mydata);
        prod = true;
        cv.notify_all();
    }
    stop = true;
    printf("Producer 结束 \n");
    cv.notify_all();
}
void Consumer1()
{
    std::unique_lock<std::mutex> lock(mtx);
    while (!stop)
    {
        CS = false;
        while (!prod && !CS)
        {
            cout << "Consumer1: cv.wait()" << endl;
            cv.wait(lock);
        }
        if(CS)
        printf("Consumer1 data: %d \n", mydata);
        prod = false;
        CS = true;
        cv.notify_all();
    }
    cv.notify_all();
}
void Consumer2()
{
    std::unique_lock<std::mutex> lock(mtx);
    while (!stop)
    {   
        CS = false;
        while (!prod && !CS)
        {
            cout << "Consumer2: cv.wait()" << endl;
            cv.wait(lock);
        }
        if(CS)
        printf("Consumer2 data: %d \n", mydata);
        prod = false;
        CS = true;
        cv.notify_all();
    }
    cv.notify_all();
}
int main()
{
    std::thread tha(Producer);
    std::thread thb1(Consumer1);
    std::thread thb2(Consumer2);
    tha.join();
    thb1.join();
    thb2.join();
    return 0;
}

在上面代码中存在三个bool值,prod用来判断生产者是否继续生产数据(容器是否满了),mydata作为容器,大小只有1,而stop用来判断生产者生产书否停止,而CS变量用来判断数据是否被其他消费者消费,因为这里的容器大小为1,所以没需要设置该变量,如果容器大小不为1,则不需要。
生产者线程:首先获得锁,进入循环,开始生产数据,如果prod为false,说明没有数据,可以进行生产数据,如果为真说明容器满了,需要等待,而获得数据之后prod设置为true,说明已经生产了数据,然后唤醒所有线程。生产结束,true。
消费者线程:同样先获取锁,接着如果生产没结束进入循环,开始消费数据,这里判断等待的条件有两个,一个是容器中有数据,一个是容器中这个数据没有被其他消费者线程消费,判断是否被其他消费者消费,结束输出数据,设置容器可生产数据,设置CS为true(该线程消费了数据,该线程进入条件变量的循环等待队列),唤醒所有线程。

例二

下面代码就是用了队列来存放数据,设置嘴啊数据值为8,封装简单的生产者消费者模型,这个代码和上面代码大同小异,就不具体说明了,值得注意的一点是在进行传参的时候是用引用,不能用值传递。

const int n = 100;
class MyQueue {
private:
    std::deque<int> qu;
    std::mutex m_cv;
    std::condition_variable cv;
    std::size_t MaxElem = 8;
public:
    MyQueue() {}
    ~MyQueue() {}
    MyQueue(const MyQueue&) = delete;
    MyQueue& operator=(const MyQueue&) = delete;
    void put(int val) {
        std::unique_lock<std::mutex> lock(m_cv);
        while (qu.size() >=MaxElem )
        {
            cv.wait(lock);
        }
        qu.push_back(val);
        cout << "Producer data: " << val << endl;
        cv.notify_all();
    }
    int get() {
        std::unique_lock<std::mutex> lock(m_cv);
        while (qu.empty())
        {
            cv.wait(lock);
        }
        int val = qu.front();
        qu.pop_front();
        cv.notify_all();
        return val;
    }

    bool size_t() {
        return qu.size();
    }
};

bool prodstop = false;
void Produrer(MyQueue& qu) {
    for (int i = 1; i <= n; ++i) {
        qu.put(i);
    }
    prodstop = true;
}
void Consumer(MyQueue& qu) {
    while(!prodstop || qu.size_t()>0) {
        int x = qu.get();
        cout << "Consumer data: " << x << endl;
    }

}
int main()
{
    MyQueue myQ;
    std::thread tha(Produrer,std::ref(myQ));
    std::thread thb1(Consumer,std::ref(myQ));
    std::thread thb2(Consumer,std::ref(myQ));
    tha.join();
    thb1.join();
    thb2.join();
    return 0;
}

死锁

什么是死锁呢?我们举一简单的例子,看代码:

void funa() {
    cout << " funa begin..." << endl;
    std::unique_lock<std::mutex> lock1(mx1);
    cout << "funa make mx1" << endl;
    std::unique_lock<std::mutex> lock2(mx2);
    cout << "funa make mx2" << endl;
}
void funb() {
    cout << " funb begin..." << endl;
    std::unique_lock<std::mutex> lock2(mx2);
    cout << "funb make mx2" << endl;
    std::unique_lock<std::mutex> lock1(mx1);
    cout << "funb make mx1" << endl;
}
int main()
{
    std::thread tha(funa);
    std::thread thb1(funb);
    tha.join();
    thb1.join();
    return 0;
}

运行结果是这样的:
在这里插入图片描述

程序死锁了,这是因为进程tha获得了锁1,thb获得了锁2,所以呢tha获得不了锁2,thb获得不了锁1,锁死了。
怎么解决呢?
下面方法呢,就是在创建了lock1和lock2的时候只是创建了对象,并没有进行加锁,lock函数便是进行加锁,要不同时获得lock1和lock2锁,要不都不获得。代码如下:

std::mutex mx1;
std::mutex mx2;
void funa() {
    cout << " funa begin..." << endl;
    std::unique_lock<std::mutex> lock1(mx1,std::defer_lock);
    std::unique_lock<std::mutex> lock2(mx2,std::defer_lock);
    std::lock(lock1, lock2);
    cout << "funa make mx1" << endl;
    cout << "funa make mx2" << endl;
}
void funb() {
    cout << " funb begin..." << endl;
    std::unique_lock<std::mutex> lock2(mx2, std::defer_lock);
    std::unique_lock<std::mutex> lock1(mx1, std::defer_lock);
    std::lock(lock2, lock1);
    cout << "funb make mx2" << endl;
    cout << "funb make mx1" << endl;
}
int main()
{
    std::thread tha(funa);
    std::thread thb1(funb);
    tha.join();
    thb1.join();
    return 0;
}

例三

举一个例子,有两张卡,A,B,卡A给卡B转钱,卡B给卡A赚钱,我们会发现其为了防止死锁是这么解决的?设置了容器,将卡的钱数放入容器中,在容器中寻找和钱数相同的用户,如果存在说明卡被占用,如果不存在可以进行转账,但是两张卡钱数一样就会发生死锁,所以呢我们可以使用卡的地址,卡的钱数可以相同,但是创建卡的地址肯定不同,所以就解决了死锁问题。

mutex mx;
condition_variable cv;
class Count
{
public:
    Count(int m) : money(m) {}
    ~Count() = default;
    bool operator<(const Count& src)const { return money < src.money; }
    int getMoney() { return money; }
    void setMoney(int m) { money = m; }
private:
    int money;
};

class Account
{
private:
    Account() = default;
    Account(const Account& src) = delete;
    Account& operator=(const Account& src) = delete;
    set<Count*> s;
public:
    ~Account() = default;
    static Account* getInstance()
    {
        static Account a;
        return &a;
    }
    void apply(Count& A, Count& B)
    {
        unique_lock<mutex> lc(mx); //申请账户 先上锁
        while (s.count(&A) > 0 || s.count(&B) > 0)
        {
            cv.wait(lc); //阻塞等待
        }
        s.insert(&A);
        s.insert(&B);
    }
    void free(Count& A, Count& B)
    {
        unique_lock<mutex> lc(mx);
        s.erase(&A);
        s.erase(&B);
        cv.notify_all();
    }

};

void thread_func1(Count& A, Count& B, int money) //A---> B转账money元
{
    Account* acc = Account::getInstance();
    acc->apply(A, B);

    if (A.getMoney() >= money)
    {
        A.setMoney(A.getMoney() - money);
        B.setMoney(B.getMoney() + money);
        cout << "successful:" << endl;
        cout << money << endl;
    }
    else
    {
        cout << "余额不足" << endl;
    }
    acc->free(A, B);
}

int main()
{
    Count A(1000);
    Count B(1000);
    thread s1(thread_func1, ref(A), ref(B), 300); //A----->B 300
    thread s2(thread_func1, ref(B), ref(A), 100); //B----->A 100
    s1.join();
    s2.join();
    cout << A.getMoney() << endl;
    cout << B.getMoney() << endl;
    system("pause");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

*闲鱼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值