前言:十分感谢字节在去年挂了我十几次的情况下,在我正式入职半年被裁了还愿意捞我一手,但是我一面就挂了,因为我不会C++多线程,我是傻逼,所以我痛定思痛,来学一手这个(这只是一方面因素,另一方面因素是现在上班好无聊啊,在摸鱼,顺手学点东西吧,一直看手机属实没啥看的,而且容易被抓包)
对于一个啥也不会的人来学习一个新的知识点,那势必先百度(白嫖公司内网的我当然选择google),我目前所在的公司C++版本是11,知道的公司大多数也是用这个C++版本
问题引入:用C++多线程实现一个生产者消费者模型
很明显,创建两个线程,一个生产一个消费,再来个互斥量就完事了(对,就是这么简单,然而我懒到了这么久都不会,我不是甩锅,我校教的属实拉了点)
那么如何创建线程,如何创建互斥量呢
我需要学习一个基础前置知识
于是google了一篇教程:C++11多线程——知乎(本来想看b站的,md,最高访问量的视频又臭又长,不过我之后还是会看一遍的 C++11多线程——bilibili,菜鸟教程的是98版本,别学)
#include
#include
using namespace std;
void threadfunction()
{
cout<<"我创建了一个线程"<
创建一个线程就是这么简单,毕竟有现成的函数库,线程不可以复制,只可以移动,
such as
thread t2(std::move(t1));
#include
#include
#include
#include // for linux
using namespace std;
std::mutex g_mutex;
void threadfunction()
{
g_mutex.lock();
cout<<"创建了一个线程,线程id 为"<
这就是加锁,什么是加锁呢,就是这个线程在执行途中不会有其他线程运行,直到该线程unlock后,释放资源了才会运行下一个线程
可以尝试注释掉加锁释放的两行代码,会发现在一个线程运行时,其他线程也会同时运行
puls版
将加锁释放改为lock_guard
为了避免线程在加锁后引发异常导致程序退出从而锁得不到释放造成的死锁问题,我们可以调用这个模板lock_guard 在作用域退出后自动释放资源(其实就是一个模板类构造加锁,析构解锁,函数异常退出了作用域也就不在了自动引发析构函数释放资源)
ok 理论上来说我们已经可以实现一个生产者消费者模型了
#include
#include
#include
#include // for linux
#include
#include
using namespace std;
mutex g_mutex;
condition_variable full, null;
int max_num = 10;
queue q;
int n = 1;
int max_buffer_size = 10;
void producer()
{
while (1)
{
{
unique_lock lock(g_mutex);
if (q.size() < max_buffer_size)
{
q.push(n);
cout << "生产了一个资源" << n << endl;
n++;
}
else
{
cout << "queue 满了 skip" << endl;
}
}
sleep(1);
}
}
void consumer()
{
while (1)
{
{
unique_lock lock(g_mutex);
if (q.size())
{
int temp = q.front();
q.pop();
cout << "消费了一个资源" << temp << endl;
}
else
cout << "没资源了,不消费了" << endl;
}
sleep(1);
}
}
int main()
{
thread t1(producer);
thread t3(producer);
thread t2(consumer);
t1.join();
t2.join();
t3.join();
}
/// (更新了一版,又摸了摸鱼,多看了几遍多线程,有了一点新的理解,这样写会更合适)
大致就是这样,但是这个时候仔细瞅瞅发现,不对啊,没了怎么能直接continue呢,我需要阻塞线程,然后等待资源池有了继续运行啊
这块的理解不够全面,当没有获取锁的时候,线程就已经会阻塞了,直至获取锁,上述代码不对的情况是,资源池满了或为空的情况下,我们的选择是skip,但是实际情况应该是当前线程需要等待直至满足条件后执行下一步
然后就引入了condition_variable
condition_variable:一般和unique_lock(可以理解为lock_guard的plus版本,但是耗费资源更多)联合使用,可以用condition_variable声明的变量主动阻塞该线程,直到其他线程唤醒它
#include
#include
#include
#include // for linux
#include
#include
using namespace std;
mutex g_mutex;
condition_variable full, null;
int max_num = 10;
queue q;
void producer()
{
int n = 1;
while (1)
{
{
unique_lock lock(g_mutex);
while (q.size() >= max_num)
{
full.wait(lock);
}
q.push(n);
cout << "生产了一个资源" << n << endl;
n++;
null.notify_one();
}
sleep(1);
}
}
void consumer()
{
while (1)
{
{
unique_lock lock(g_mutex);
while (!q.size())
{
null.wait(lock);
}
int temp = q.front();
q.pop();
cout << "消费了一个资源" << temp << endl;
full.notify_one();
}
sleep(1);
}
}
int main()
{
thread t1(producer);
thread t2(consumer);
t1.join();
t2.join();
}
然后就改成这样了,可是我润的不对啊,为什么是生产满,消费完,生产满,消费完的流程啊
有没有巨巨教教我,我是fw啊!
嘿嘿 我改好了 sleep的时机不对
ok,接下来就是单生产者多消费者的模式,那么相对于上面这个单消费者单生产者的模式有什么区别呢,就是多个消费者线程在同时执行时需要共享变量
好吧,如果这样写的话直接加生产者线程,消费者线程就可以了,因为我的做法是直接对整个队列加锁,这样就保证了同时只有一个线程可以访问/修改该队列,但是这样做效率并不高,因为理论上来说,当队列中有资源时,同时生产和消费是没问题的,所以plus版本可以申请两个变量,用数组模拟队列,这样就可以实现同时生产,消费了,然后多消费者的情况就得对变量加锁
ok,接下来还有一点自己的理解,就是mutex就是锁,然后目前我写的demo都是对应着一个变量,也就是在加锁期间,该变量只能被一个线程使用,这个变量此时就是一个互斥量了,然而那个mutex对应哪个变量就是需要程序员自己掌握的了