创建多个线程和等待多个线程
#include<iostream>
#include <thread>
#include<vector>
using namespace std;
void print(int num)
{
cout << "print执行,线程编号:" << num << endl;
cout << "print结束,线程编号:" << num << endl;
return;
}
int main()
{
vector<thread>v;
for (int i = 0; i < 10; ++i)
{
v.push_back (thread(print, i));//创建10个线程并开始执行线程
}
for (auto iter = v.begin(); iter != v.end(); ++iter)
{
iter->join();
}
cout << "hello!" << endl;
return 0;
}
可以得出结论:
1.多个线程执行的顺序是乱的,跟操作系统内部调度机制有关。
2.主线程等待所有子线程运行结束以后,最后主线程结束,推荐这种写法,更容易写出稳定的程序。
…
…
数据共享问题分析
1.只读数据不修改
void print(int num)
{
cout << "线程id: " << this_thread::get_id() << ", " << vals[0] << vals[1] << vals[2] << endl;
return;
}
int main()
{
vector<thread>v;
for (int i = 0; i < 10; ++i)
{
v.push_back (thread(print, i));//创建10个线程并开始执行线程
}
for (auto iter = v.begin(); iter != v.end(); ++iter)
{
iter->join();
}
cout << "hello!" << endl;
return 0;
}
可以看到虽然顺序是不稳定的,但是每次都成功打印了,只读数据是安全稳定的,不需要什么处理手段.
实际案例
class A
{
private:
list<int>msgqueue;
public:
void MsgEnqueue()
{
for (int i = 0; i < 10000; ++i)
{
cout << "MsgEnqueue()执行,插入一个元素 " << i << endl;
msgqueue.push_back(i);
}
}
void MsgDequeue()
{
for (int i = 0; i < 10000; ++i)
{
if (!msgqueue.empty())
{
int command = msgqueue.front();
msgqueue.pop_front();
}
else
{
cout << "MsgEnqueue()执行,但队列为空" << endl;
}
}
}
};
int main()
{
A a;
thread t1(&A::MsgEnqueue, &a);
thread t2(&A::MsgDequeue, &a);
t1.join();
t2.join();
cout << endl;
}
可以看到由于没有保护,一个线程疯狂入队,另一个线程疯狂出队。程序运行时很快就崩溃了。
解决办法:保护共享数据,操作时,操作时,某个线程用代码把共享数据锁住,其他想操作共享数据的线程必须等待解锁。
互斥量
互斥量是一个类对象,理解成一把锁,多个线程尝试使用Lock()
成员函数来加锁这把锁头,只有一个线程能锁定成功,如果没锁成功,流程会卡在lock()这里不断的尝试去加锁。互斥量使用要小心,保护数据少了,达不到保护的效果,多了影响效率
头文件<mutex>
lock()和unlock()
使用方法:
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
class A
{
private:
list<int>msgqueue;
mutex mymutex;
public:
void MsgEnqueue()
{
for (int i = 0; i < 10000; ++i)
{
cout << "MsgEnqueue()执行,插入一个元素 " << i << endl;
mymutex.lock();
msgqueue.push_back(i);
mymutex.unlock();
}
return;
}
bool helper(int & command)
{
mymutex.lock();
if (!msgqueue.empty())
{
command = msgqueue.front();
msgqueue.pop_front();
mymutex.unlock();
return true;
}
mymutex.unlock();
return false;
}
void MsgDequeue()
{
int command = 0;
for (int i = 0; i < 10000; ++i)
{
bool result = helper(command);
if (result)
{
cout << "helper执行,取出一个元素 " << command << endl;
}
else
{
cout << "MsgEnqueue()执行,但队列为空 " << i <<endl;
}
}
}
};
int main()
{
A a;
thread t1(&A::MsgEnqueue, &a);
thread t2(&A::MsgDequeue, &a);
t1.join();
t2.join();
cout << endl;
}
这个时候运行就不会出错了。
使用总结:
1.先lock(),在unlock().
2. lock()和unlock()要成对执行,如果是有多个分支比如if else这种,每个分支都要unlock,因为是多个出口.
…
…
lock_guard
使用方法
为了防止忘记unlock,提供了lock_guard
更方便的使用方法
工作原理:类似智能指针,构造的时候调用锁的lock()函数,析构的时候调用unlock()函数,作用域结束的时候也就自动销毁调用unlock().
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
class A
{
private:
list<int>msgqueue;
mutex mymutex;
public:
void MsgEnqueue()
{
for (int i = 0; i < 10000; ++i)
{
cout << "MsgEnqueue()执行,插入一个元素 " << i << endl;
mymutex.lock();
msgqueue.push_back(i);
mymutex.unlock();
}
return;
}
bool helper(int & command)
{
lock_guard<mutex>a(mymutex);
if (!msgqueue.empty())
{
command = msgqueue.front();
msgqueue.pop_front();
//mymutex.unlock();
return true;
}
//mymutex.unlock();
return false;
}
void MsgDequeue()
{
int command = 0;
for (int i = 0; i < 10000; ++i)
{
bool result = helper(command);
if (result)
{
cout << "helper执行,取出一个元素 " << command << endl;
}
else
{
cout << "MsgEnqueue()执行,但队列为空 " << i <<endl;
}
}
}
};
int main()
{
A a;
thread t1(&A::MsgEnqueue, &a);
thread t2(&A::MsgDequeue, &a);
t1.join();
t2.join();
cout << endl;
}
可以加个花括号,缩小作用域从而提前析构。
好处:使用简单,不怕忘记unlock()。
缺点:不够灵活,不能随时unlock(),只有析构的时候才能解锁,不能更精确的控制加锁和解锁。
…
…
…
死锁
两个或两个以上锁(互斥量)会有可能造成死锁问题
产生的原因,举个例子:
假设有两把锁1和锁2,有两个线程A和B:
1.线程A执行,先加锁锁1,锁1lock()成功,正打算lock()锁2.
然后上下文切换
2.线程B执行,先加锁锁2,锁2lock()成功,正打算lock()锁1.
此时此刻,死锁就产生了。
线程A因为加锁不了锁2,流程走不下去。
线程B因为加锁不了锁1,流程也走不下去。
就这样僵持住了,导致死锁。
死锁演示:
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
class A
{
private:
list<int>msgqueue;
mutex mymutex1;
mutex mymutex2;
public:
void MsgEnqueue()
{
for (int i = 0; i < 10000; ++i)
{
cout << "MsgEnqueue()执行,插入一个元素 " << i << endl;
mymutex1.lock();
mymutex2.lock();
msgqueue.push_back(i);
mymutex2.unlock();
mymutex1.unlock();
}
return;
}
bool helper(int & command)
{
mymutex2.lock();
mymutex1.lock();
if (!msgqueue.empty())
{
command = msgqueue.front();
msgqueue.pop_front();
mymutex2.unlock();
mymutex1.unlock();
return true;
}
mymutex2.unlock();
mymutex1.unlock();
return false;
}
void MsgDequeue()
{
int command = 0;
for (int i = 0; i < 10000; ++i)
{
bool result = helper(command);
if (result)
{
cout << "helper执行,取出一个元素 " << command << endl;
}
else
{
cout << "MsgEnqueue()执行,但队列为空 " << i <<endl;
}
}
}
};
int main()
{
A a;
thread t1(&A::MsgEnqueue, &a);
thread t2(&A::MsgDequeue, &a);
t1.join();
t2.join();
cout << endl;
}
可以明确看到程序卡住不动了,死锁了。
解决死锁的办法:保证相同的上锁顺序,比如线程A先lock锁1在lock锁2,那线程B也应该先lock锁1在lock锁2.
std::lock()函数模板
能力:一次锁住两个或两个以上的互斥量(至少两个,多了也不行),不存在在多线程中,因为锁的顺序而造成死锁的风险。如果互斥量中有一个没锁住,就等待所以互斥量锁住,要么多个锁都锁住了,要么都不锁,如果其他中一个锁锁住了,另外一个上锁失败,那就会把其他的锁也解锁。
使用例子:
class A
{
private:
list<int>msgqueue;
mutex mymutex1;
mutex mymutex2;
public:
void MsgEnqueue()
{
for (int i = 0; i < 10000; ++i)
{
cout << "MsgEnqueue()执行,插入一个元素 " << i << endl;
lock(mymutex1, mymutex2);
msgqueue.push_back(i);
mymutex2.unlock();
mymutex1.unlock();
}
return;
}
bool helper(int & command)
{
lock(mymutex1, mymutex2);
if (!msgqueue.empty())
{
command = msgqueue.front();
msgqueue.pop_front();
mymutex2.unlock();
mymutex1.unlock();
return true;
}
mymutex2.unlock();
mymutex1.unlock();
return false;
}
void MsgDequeue()
{
int command = 0;
for (int i = 0; i < 10000; ++i)
{
bool result = helper(command);
if (result)
{
cout << "helper执行,取出一个元素 " << command << endl;
}
else
{
cout << "MsgEnqueue()执行,但队列为空 " << i <<endl;
}
}
}
};
int main()
{
A a;
thread t1(&A::MsgEnqueue, &a);
thread t2(&A::MsgDequeue, &a);
t1.join();
t2.join();
cout << endl;
}
缺点:还是需要手动unlock很有可能会忘记。
解决办法:使用lock_guard
配合std::adopt_lock
,自动调用unlock
使用例子:
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
class A
{
private:
list<int>msgqueue;
mutex mymutex1;
mutex mymutex2;
public:
void MsgEnqueue()
{
for (int i = 0; i < 10000; ++i)
{
cout << "MsgEnqueue()执行,插入一个元素 " << i << endl;
lock(mymutex1, mymutex2);
lock_guard<mutex>(mymutex1, std::adopt_lock);
lock_guard<mutex>(mymutex2, std::adopt_lock);
msgqueue.push_back(i);
}
return;
}
bool helper(int & command)
{
lock(mymutex1, mymutex2);
lock_guard<mutex>(mymutex1, std::adopt_lock);
lock_guard<mutex>(mymutex2, std::adopt_lock);
if (!msgqueue.empty())
{
command = msgqueue.front();
msgqueue.pop_front();
return true;
}
return false;
}
void MsgDequeue()
{
int command = 0;
for (int i = 0; i < 10000; ++i)
{
bool result = helper(command);
if (result)
{
cout << "helper执行,取出一个元素 " << command << endl;
}
else
{
cout << "MsgEnqueue()执行,但队列为空 " << i <<endl;
}
}
}
};
int main()
{
A a;
thread t1(&A::MsgEnqueue, &a);
thread t2(&A::MsgDequeue, &a);
t1.join();
t2.join();
cout << endl;
}