創建多個線程和等待多個線程
#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;
}