詳解C語言編程之thread多線程
線程創(chuàng)建與結(jié)束
C++11 新標(biāo)準(zhǔn)中引入了四個(gè)頭文件來支持多線程編程,他們分別是<atomic> ,<thread>,<mutex>,<condition_variable>和<future>。
<atomic>:該頭文主要聲明了兩個(gè)類, std::atomic 和 std::atomic_flag,另外還聲明了一套 C 風(fēng)格的原子類型和與 C 兼容的原子操作的函數(shù)。<thread>:該頭文件主要聲明了 std::thread 類,另外 std::this_thread 命名空間也在該頭文件中。<mutex>:該頭文件主要聲明了與互斥量(mutex)相關(guān)的類,包括 std::mutex 系列類,std::lock_guard, std::unique_lock, 以及其他的類型和函數(shù)。<condition_variable>:該頭文件主要聲明了與條件變量相關(guān)的類,包括 std::condition_variable 和 std::condition_variable_any。<future>:該頭文件主要聲明了 std::promise, std::package_task 兩個(gè) Provider 類,以及 std::future 和 std::shared_future 兩個(gè) Future 類,另外還有一些與之相關(guān)的類型和函數(shù),std::async() 函數(shù)就聲明在此頭文件中。
#include <iostream>
#include <utility>
#include <thread>
#include <chrono>
#include <functional>
#include <atomic>
void f1(int n)
{
for (int i = 0; i < 5; ++i) {
std::cout << "Thread " << n << " executing\n";
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
}
void f2(int& n)
{
std::cout << "thread-id:" << std::this_thread::get_id() << "\n";
for (int i = 0; i < 5; ++i) {
std::cout << "Thread 2 executing:" << n << "\n";
++n;
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
}
int main()
{
int n = 0;
std::thread t1; // t1 is not a thread t1 不是一個(gè)線程
std::thread t2(f1, n + 1); // pass by value 傳值
std::thread t3(f2, std::ref(n)); // pass by reference 傳引用
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
std::cout << "\nThread 4 create :\n";
std::thread t4(std::move(t3)); // t4 is now running f2(). t3 is no longer a thread 這時(shí)候t3將不是線程,t4接替t3繼續(xù)運(yùn)行f2
t2.join();
t4.join();
std::cout << "Final value of n is " << n << '\n';
}
線程的創(chuàng)建方式:
(1). 默認(rèn)構(gòu)造函數(shù),創(chuàng)建一個(gè)空的 thread 執(zhí)行對象。
(2). 初始化構(gòu)造函數(shù),創(chuàng)建一個(gè) thread對象,該 thread對象可被 joinable,新產(chǎn)生的線程會調(diào)用 fn 函數(shù),該函數(shù)的參數(shù)由 args 給出。
(3). 拷貝構(gòu)造函數(shù)(被禁用),意味著 thread 不可被拷貝構(gòu)造。
(4). move 構(gòu)造函數(shù),move 構(gòu)造函數(shù),調(diào)用成功之后 x 不代表任何 thread 執(zhí)行對象。
注意:可被 joinable 的 thread 對象必須在他們銷毀之前被主線程 join 或者將其設(shè)置為 detached.
std::thread定義一個(gè)線程對象,傳入線程所需要的線程函數(shù)和參數(shù),線程自動(dòng)開啟
線程的結(jié)束方式:
join()
創(chuàng)建線程執(zhí)行線程函數(shù),調(diào)用該函數(shù)會阻塞當(dāng)前線程,直到線程執(zhí)行完join才返回;等待t線程結(jié)束,當(dāng)前線程繼續(xù)往下運(yùn)行
detach()
detach調(diào)用之后,目標(biāo)線程就成為了守護(hù)線程,駐留后臺運(yùn)行,與之關(guān)聯(lián)的std::thread對象失去對目標(biāo)線程的關(guān)聯(lián),無法再通過std::thread對象取得該線程的控制權(quán),由操作系統(tǒng)負(fù)責(zé)回收資源;主線程結(jié)束,整個(gè)進(jìn)程結(jié)束,所有子線程都自動(dòng)結(jié)束了!
#include <iostream>
#include <thread>
using namespace std;
void threadHandle1(int time)
{
//讓子線程睡眠time秒
std::this_thread::sleep_for(std::chrono::seconds(time));
cout << "hello thread1!" << endl;
}
void threadHandle2(int time)
{
//讓子線程睡眠time秒ace this_thread是namespace
std::this_thread::sleep_for(std::chrono::seconds(time));
cout << "hello thread2!" << endl;
}
int main()
{
//創(chuàng)建了一個(gè)線程對象,傳入一個(gè)線程函數(shù)(作為線程入口函數(shù)),
//新線程就開始運(yùn)行了,沒有先后順序,隨著CPU的調(diào)度算法執(zhí)行
std::thread t1(threadHandle1, 2);
std::thread t2(threadHandle2, 3);
//主線程(main)運(yùn)行到這里,等待子線程結(jié)束,主線程才繼續(xù)往下運(yùn)行
t1.join();
t2.join();
//把子線程設(shè)置為分離線程,子線程和主線程就毫無關(guān)系了
//主線程結(jié)束的時(shí)候查看其他線程
//但是這個(gè)子線程運(yùn)行完還是沒運(yùn)行完都和這個(gè)主線程沒關(guān)系了
//這個(gè)子線程就從這個(gè)main分離出去了
//運(yùn)行程序時(shí)也看不到這個(gè)子線程的任何輸出打印了
//t1.detach();
cout << "main thread done!" << endl;
//主線程運(yùn)行完成,查看如果當(dāng)前進(jìn)程還有未運(yùn)行完成的子線程
//進(jìn)程就會異常終止
return 0;
}
互斥鎖
Mutex 又稱互斥量,C++ 11中與 Mutex 相關(guān)的類(包括鎖類型)和函數(shù)都聲明在 <mutex> 頭文件中,所以如果你需要使用 std::mutex,就必須包含 <mutex> 頭文件。
<mutex> 頭文件介紹
Mutex 系列類(四種)
std::mutex,最基本的 Mutex 類。std::recursive_mutex,遞歸 Mutex 類。std::time_mutex,定時(shí) Mutex 類。std::recursive_timed_mutex,定時(shí)遞歸 Mutex 類。
Lock 類(兩種)
std::lock_guard,與 Mutex RAII 相關(guān),方便線程對互斥量上鎖。
std::unique_lock,與 Mutex RAII 相關(guān),方便線程對互斥量上鎖,但提供了更好的上鎖和解鎖控制。
其他類型
std::once_flagstd::adopt_lock_tstd::defer_lock_tstd::try_to_lock_t
函數(shù)
std::try_lock,嘗試同時(shí)對多個(gè)互斥量上鎖。std::lock,可以同時(shí)對多個(gè)互斥量上鎖。std::call_once,如果多個(gè)線程需要同時(shí)調(diào)用某個(gè)函數(shù),call_once 可以保證多個(gè)線程對該函數(shù)只調(diào)用一次。
std::mutex 介紹
下面以 std::mutex 為例介紹 C++11 中的互斥量用法。
std::mutex 是C++11 中最基本的互斥量,std::mutex 對象提供了獨(dú)占所有權(quán)的特性——即不支持遞歸地對 std::mutex 對象上鎖,而 std::recursive_lock 則可以遞歸地對互斥量對象上鎖。
std::mutex 的成員函數(shù)
- 構(gòu)造函數(shù),std::mutex不允許拷貝構(gòu)造,也不允許 move 拷貝,最初產(chǎn)生的 mutex 對象是處于 unlocked 狀態(tài)的。
- lock(),調(diào)用線程將鎖住該互斥量。線程調(diào)用該函數(shù)會發(fā)生下面 3 種情況:
(1). 如果該互斥量當(dāng)前沒有被鎖住,則調(diào)用線程將該互斥量鎖住,直到調(diào)用 unlock之前,該線程一直擁有該鎖。
(2). 如果當(dāng)前互斥量被其他線程鎖住,則當(dāng)前的調(diào)用線程被阻塞住。
(3). 如果當(dāng)前互斥量被當(dāng)前調(diào)用線程鎖住,則會產(chǎn)生死鎖(deadlock)。
- unlock(), 解鎖,釋放對互斥量的所有權(quán)。
- try_lock(),嘗試鎖住互斥量,如果互斥量被其他線程占有,則當(dāng)前線程也不會被阻塞。線程調(diào)用該函數(shù)也會出現(xiàn)下面 3 種情況
(1). 如果當(dāng)前互斥量沒有被其他線程占有,則該線程鎖住互斥量,直到該線程調(diào)用 unlock 釋放互斥量。
(2). 如果當(dāng)前互斥量被其他線程鎖住,則當(dāng)前調(diào)用線程返回 false,而并不會被阻塞掉。
(3). 如果當(dāng)前互斥量被當(dāng)前調(diào)用線程鎖住,則會產(chǎn)生死鎖(deadlock)
為了保證lock()和unlock()對應(yīng)使用,一般不直接使用mutex,而是和lock_guard、unique_lock一起使用;
std::lock_guard
std::lock_guard是RAII模板類的簡單實(shí)現(xiàn),功能簡單。
1.std::lock_guard 在構(gòu)造函數(shù)中進(jìn)行加鎖,析構(gòu)函數(shù)中進(jìn)行解鎖。
// CLASS TEMPLATE lock_guard
template<class _Mutex>
class lock_guard
{ // class with destructor that unlocks a mutex
public:
using mutex_type = _Mutex;
explicit lock_guard(_Mutex& _Mtx)
: _MyMutex(_Mtx)
{ // construct and lock
_MyMutex.lock();
}
lock_guard(_Mutex& _Mtx, adopt_lock_t)
: _MyMutex(_Mtx)
{ // construct but don't lock
}
~lock_guard() noexcept
{ // unlock
_MyMutex.unlock();
}
lock_guard(const lock_guard&) = delete;
lock_guard& operator=(const lock_guard&) = delete;
private:
_Mutex& _MyMutex;
};
從lock_guard源碼可以看出,它在構(gòu)造時(shí)進(jìn)行上鎖,在出作用域執(zhí)行析構(gòu)函數(shù)釋放鎖;同時(shí)不允許拷貝構(gòu)造和賦值運(yùn)算符;比較簡單,不能用在函數(shù)參數(shù)傳遞或者返回過程中,因?yàn)樗目截悩?gòu)造和賦值運(yùn)算符被禁用了;只能用在簡單的臨界區(qū)代碼的互斥操作
std::unique_lock
類 unique_lock 是通用互斥包裝器,允許延遲鎖定、鎖定的有時(shí)限嘗試、遞歸鎖定、所有權(quán)轉(zhuǎn)移和與條件變量一同使用。
unique_lock比lock_guard使用更加靈活,功能更加強(qiáng)大。
使用unique_lock需要付出更多的時(shí)間、性能成本。
template<class _Mutex>
class unique_lock
{ // whizzy class with destructor that unlocks mutex
public:
typedef _Mutex mutex_type;
// CONSTRUCT, ASSIGN, AND DESTROY
unique_lock() noexcept
: _Pmtx(nullptr), _Owns(false)
{ // default construct
}
explicit unique_lock(_Mutex& _Mtx)
: _Pmtx(_STD addressof(_Mtx)), _Owns(false)
{ // construct and lock
_Pmtx->lock();
_Owns = true;
}
unique_lock(_Mutex& _Mtx, adopt_lock_t)
: _Pmtx(_STD addressof(_Mtx)), _Owns(true)
{ // construct and assume already locked
}
unique_lock(_Mutex& _Mtx, defer_lock_t) noexcept
: _Pmtx(_STD addressof(_Mtx)), _Owns(false)
{ // construct but don't lock
}
unique_lock(_Mutex& _Mtx, try_to_lock_t)
: _Pmtx(_STD addressof(_Mtx)), _Owns(_Pmtx->try_lock())
{ // construct and try to lock
}
template<class _Rep,
class _Period>
unique_lock(_Mutex& _Mtx,
const chrono::duration<_Rep, _Period>& _Rel_time)
: _Pmtx(_STD addressof(_Mtx)), _Owns(_Pmtx->try_lock_for(_Rel_time))
{ // construct and lock with timeout
}
template<class _Clock,
class _Duration>
unique_lock(_Mutex& _Mtx,
const chrono::time_point<_Clock, _Duration>& _Abs_time)
: _Pmtx(_STD addressof(_Mtx)), _Owns(_Pmtx->try_lock_until(_Abs_time))
{ // construct and lock with timeout
}
unique_lock(_Mutex& _Mtx, const xtime *_Abs_time)
: _Pmtx(_STD addressof(_Mtx)), _Owns(false)
{ // try to lock until _Abs_time
_Owns = _Pmtx->try_lock_until(_Abs_time);
}
unique_lock(unique_lock&& _Other) noexcept
: _Pmtx(_Other._Pmtx), _Owns(_Other._Owns)
{ // destructive copy
_Other._Pmtx = nullptr;
_Other._Owns = false;
}
unique_lock& operator=(unique_lock&& _Other)
{ // destructive copy
if (this != _STD addressof(_Other))
{ // different, move contents
if (_Owns)
_Pmtx->unlock();
_Pmtx = _Other._Pmtx;
_Owns = _Other._Owns;
_Other._Pmtx = nullptr;
_Other._Owns = false;
}
return (*this);
}
~unique_lock() noexcept
{ // clean up
if (_Owns)
_Pmtx->unlock();
}
unique_lock(const unique_lock&) = delete;
unique_lock& operator=(const unique_lock&) = delete;
void lock()
{ // lock the mutex
_Validate();
_Pmtx->lock();
_Owns = true;
}
_NODISCARD bool try_lock()
{ // try to lock the mutex
_Validate();
_Owns = _Pmtx->try_lock();
return (_Owns);
}
template<class _Rep,
class _Period>
_NODISCARD bool try_lock_for(const chrono::duration<_Rep, _Period>& _Rel_time)
{ // try to lock mutex for _Rel_time
_Validate();
_Owns = _Pmtx->try_lock_for(_Rel_time);
return (_Owns);
}
template<class _Clock,
class _Duration>
_NODISCARD bool try_lock_until(const chrono::time_point<_Clock, _Duration>& _Abs_time)
{ // try to lock mutex until _Abs_time
_Validate();
_Owns = _Pmtx->try_lock_until(_Abs_time);
return (_Owns);
}
_NODISCARD bool try_lock_until(const xtime *_Abs_time)
{ // try to lock the mutex until _Abs_time
_Validate();
_Owns = _Pmtx->try_lock_until(_Abs_time);
return (_Owns);
}
void unlock()
{ // try to unlock the mutex
if (!_Pmtx || !_Owns)
_THROW(system_error(
_STD make_error_code(errc::operation_not_permitted)));
_Pmtx->unlock();
_Owns = false;
}
void swap(unique_lock& _Other) noexcept
{ // swap with _Other
_STD swap(_Pmtx, _Other._Pmtx);
_STD swap(_Owns, _Other._Owns);
}
_Mutex *release() noexcept
{ // disconnect
_Mutex *_Res = _Pmtx;
_Pmtx = nullptr;
_Owns = false;
return (_Res);
}
_NODISCARD bool owns_lock() const noexcept
{ // return true if this object owns the lock
return (_Owns);
}
explicit operator bool() const noexcept
{ // return true if this object owns the lock
return (_Owns);
}
_NODISCARD _Mutex *mutex() const noexcept
{ // return pointer to managed mutex
return (_Pmtx);
}
private:
_Mutex *_Pmtx;
bool _Owns;
void _Validate() const
{ // check if the mutex can be locked
if (!_Pmtx)
_THROW(system_error(
_STD make_error_code(errc::operation_not_permitted)));
if (_Owns)
_THROW(system_error(
_STD make_error_code(errc::resource_deadlock_would_occur)));
}
};
其中,有_Mutex *_Pmtx; 指向一把鎖的指針;不允許使用左值拷貝構(gòu)造和賦值,但是可以使用右值拷貝構(gòu)造和賦值,可以在函數(shù)調(diào)用過程中使用。因此可以和條件變量一起使用:cv.wait(lock);//可以作為函數(shù)參數(shù)傳入;
示例:
在多線程環(huán)境中運(yùn)行的代碼段,需要考慮是否存在競態(tài)條件,如果存在競態(tài)條件,我們就說該代碼段不是線程安全的,不能直接運(yùn)行在多線程環(huán)境當(dāng)中,對于這樣的代碼段,我們經(jīng)常稱之為臨界區(qū)資源,對于臨界區(qū)資源,多線程環(huán)境下需要保證它以原子操作執(zhí)行,要保證臨界區(qū)的原子操作,就需要用到線程間的互斥操作-鎖機(jī)制,thread類庫還提供了更輕量級的基于CAS操作的原子操作類。
無鎖時(shí):
?
#include <iostream>
#include <atomic>//C++11線程庫提供的原子類
#include <thread>//C++線程類庫的頭文件
#include <vector>
int count = 0;
//線程函數(shù)
void sumTask()
{
//每個(gè)線程給count加10次
for (int i = 0; i < 10; ++i)
{
count++;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
int main()
{
//創(chuàng)建10個(gè)線程放在容器當(dāng)中
std::vector<std::thread> vec;
for (int i = 0; i < 10; ++i)
{
vec.push_back(std::thread(sumTask));
}
//等待線程執(zhí)行完成
for (unsigned int i = 0; i < vec.size(); ++i)
{
vec[i].join();
}
//所有子線程運(yùn)行結(jié)束
std::cout << "count : " << count << std::endl;
return 0;
}
?
多線程同時(shí)對count進(jìn)行操作,并不能保證同時(shí)只有一個(gè)線程對count執(zhí)行++操作,最后的的結(jié)果不一定是100;
使用lock_guard:
#include <iostream>
#include <atomic>//C++11線程庫提供的原子類
#include <thread>//C++線程類庫的頭文件
#include <mutex>
#include <vector>
int count = 0;
std::mutex mutex;
//線程函數(shù)
void sumTask()
{
//每個(gè)線程給count加10次
for (int i = 0; i < 10; ++i)
{
{
std::lock_guard<std::mutex> lock(mutex);
count++;
}
;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
int main()
{
//創(chuàng)建10個(gè)線程放在容器當(dāng)中
std::vector<std::thread> vec;
for (int i = 0; i < 10; ++i)
{
vec.push_back(std::thread(sumTask));
}
//等待線程執(zhí)行完成
for (unsigned int i = 0; i < vec.size(); ++i)
{
vec[i].join();
}
//所有子線程運(yùn)行結(jié)束,count的結(jié)果每次運(yùn)行應(yīng)該都是10000
std::cout << "count : " << count << std::endl;
return 0;
}
對count++ 操作上鎖,保證一次只有一個(gè)線程能對其操作,結(jié)果是100
原子變量
上面的保證原子操作需要在多線程環(huán)境下添加互斥操作,但是mutex互斥鎖畢竟比較重,對于系統(tǒng)消耗有些大,C++11的thread類庫提供了針對簡單類型的原子操作類,如std::atomic_int,atomic_long,atomic_bool等,它們值的增減都是基于CAS操作的,既保證了線程安全,效率還非常高。
#include <iostream>
#include <atomic>//C++11線程庫提供的原子類
#include <thread>//C++線程類庫的頭文件
#include <vector>
//原子整型,CAS操作保證給count自增自減的原子操作
std::atomic_int count = 0;
//線程函數(shù)
void sumTask()
{
//每個(gè)線程給count加10次
for (int i = 0; i < 10; ++i)
{
count++;
}
}
int main()
{
//創(chuàng)建10個(gè)線程放在容器當(dāng)中
std::vector<std::thread> vec;
for (int i = 0; i < 10; ++i)
{
vec.push_back(std::thread(sumTask));
}
//等待線程執(zhí)行完成
for (unsigned int i = 0; i < vec.size(); ++i)
{
vec[i].join();
}
//所有子線程運(yùn)行結(jié)束,count的結(jié)果每次運(yùn)行應(yīng)該都是10000
std::cout << "count : " << count << std::endl;
return 0;
}
線程同步通信
多線程在運(yùn)行過程中,各個(gè)線程都是隨著OS的調(diào)度算法,占用CPU時(shí)間片來執(zhí)行指令做事情,每個(gè)線程的運(yùn)行完全沒有順序可言。但是在某些應(yīng)用場景下,一個(gè)線程需要等待另外一個(gè)線程的運(yùn)行結(jié)果,才能繼續(xù)往下執(zhí)行,這就需要涉及線程之間的同步通信機(jī)制。
線程間同步通信最典型的例子就是生產(chǎn)者-消費(fèi)者模型,生產(chǎn)者線程生產(chǎn)出產(chǎn)品以后,會通知消費(fèi)者線程去消費(fèi)產(chǎn)品;如果消費(fèi)者線程去消費(fèi)產(chǎn)品,發(fā)現(xiàn)還沒有產(chǎn)品生產(chǎn)出來,它需要通知生產(chǎn)者線程趕快生產(chǎn)產(chǎn)品,等生產(chǎn)者線程生產(chǎn)出產(chǎn)品以后,消費(fèi)者線程才能繼續(xù)往下執(zhí)行。
C++11 線程庫提供的條件變量condition_variable,就是Linux平臺下的Condition Variable機(jī)制,用于解決線程間的同步通信問題,下面通過代碼演示一個(gè)生產(chǎn)者-消費(fèi)者線程模型:
#include <iostream> //std::cout
#include <thread> //std::thread
#include <mutex> //std::mutex, std::unique_lock
#include <condition_variable> //std::condition_variable
#include <vector>
//定義互斥鎖(條件變量需要和互斥鎖一起使用)
std::mutex mtx;
//定義條件變量(用來做線程間的同步通信)
std::condition_variable cv;
//定義vector容器,作為生產(chǎn)者和消費(fèi)者共享的容器
std::vector<int> vec;
//生產(chǎn)者線程函數(shù)
void producer()
{
//生產(chǎn)者每生產(chǎn)一個(gè),就通知消費(fèi)者消費(fèi)一個(gè)
for (int i = 1; i <= 10; ++i)
{
//獲取mtx互斥鎖資源
std::unique_lock<std::mutex> lock(mtx);
//如果容器不為空,代表還有產(chǎn)品未消費(fèi),等待消費(fèi)者線程消費(fèi)完,再生產(chǎn)
while (!vec.empty())
{
//判斷容器不為空,進(jìn)入等待條件變量的狀態(tài),釋放mtx鎖,
//讓消費(fèi)者線程搶到鎖能夠去消費(fèi)產(chǎn)品
cv.wait(lock);
}
vec.push_back(i); // 表示生產(chǎn)者生產(chǎn)的產(chǎn)品序號i
std::cout << "producer生產(chǎn)產(chǎn)品:" << i << std::endl;
/*
生產(chǎn)者線程生產(chǎn)完產(chǎn)品,通知等待在cv條件變量上的消費(fèi)者線程,
可以開始消費(fèi)產(chǎn)品了,然后釋放鎖mtx
*/
cv.notify_all();
//生產(chǎn)一個(gè)產(chǎn)品,睡眠100ms
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
//消費(fèi)者線程函數(shù)
void consumer()
{
//消費(fèi)者每消費(fèi)一個(gè),就通知生產(chǎn)者生產(chǎn)一個(gè)
for (int i = 1; i <= 10; ++i)
{
//獲取mtx互斥鎖資源
std::unique_lock<std::mutex> lock(mtx);
//如果容器為空,代表還有沒有產(chǎn)品可消費(fèi),等待生產(chǎn)者生產(chǎn),再消費(fèi)
while (vec.empty())
{
//判斷容器為空,進(jìn)入等待條件變量的狀態(tài),釋放mtx鎖,
//讓生產(chǎn)者線程搶到鎖能夠去生產(chǎn)產(chǎn)品
cv.wait(lock);
}
int data = vec.back(); // 表示消費(fèi)者消費(fèi)的產(chǎn)品序號i
vec.pop_back();
std::cout << "consumer消費(fèi)產(chǎn)品:" << data << std::endl;
/*
消費(fèi)者消費(fèi)完產(chǎn)品,通知等待在cv條件變量上的生產(chǎn)者線程,
可以開始生產(chǎn)產(chǎn)品了,然后釋放鎖mtx
*/
cv.notify_all();
//消費(fèi)一個(gè)產(chǎn)品,睡眠100ms
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
int main()
{
//創(chuàng)建生產(chǎn)者和消費(fèi)者線程
std::thread t1(producer);
std::thread t2(consumer);
//main主線程等待所有子線程執(zhí)行完
t1.join();
t2.join();
return 0;
}
線程死鎖
死鎖概述
線程死鎖是指兩個(gè)或兩個(gè)以上的線程互相持有對方所需要的資源,由于synchronized的特性,一個(gè)線程持有一個(gè)資源,或者說獲得一個(gè)鎖,在該線程釋放這個(gè)鎖之前,其它線程是獲取不到這個(gè)鎖的,而且會一直死等下去,因此這便造成了死鎖。
死鎖產(chǎn)生的條件
- 互斥條件:一個(gè)資源,或者說一個(gè)鎖只能被一個(gè)線程所占用,當(dāng)一個(gè)線程首先獲取到這個(gè)鎖之后,在該線程釋放這個(gè)鎖之前,其它線程均是無法獲取到這個(gè)鎖的。
- 占有且等待:一個(gè)線程已經(jīng)獲取到一個(gè)鎖,再獲取另一個(gè)鎖的過程中,即使獲取不到也不會釋放已經(jīng)獲得的鎖。
- 不可剝奪條件:任何一個(gè)線程都無法強(qiáng)制獲取別的線程已經(jīng)占有的鎖
- 循環(huán)等待條件:線程A拿著線程B的鎖,線程B拿著線程A的鎖。
示例:
當(dāng)一個(gè)程序的多個(gè)線程獲取多個(gè)互斥鎖資源的時(shí)候,就有可能發(fā)生死鎖問題,比如線程A先獲取了鎖1,線程B獲取了鎖2,進(jìn)而線程A還需要獲取鎖2才能繼續(xù)執(zhí)行,但是由于鎖2被線程B持有還沒有釋放,線程A為了等待鎖2資源就阻塞了;線程B這時(shí)候需要獲取鎖1才能往下執(zhí)行,但是由于鎖1被線程A持有,導(dǎo)致A也進(jìn)入阻塞。
線程A和線程B都在等待對方釋放鎖資源,但是它們又不肯釋放原來的鎖資源,導(dǎo)致線程A和B一直互相等待,進(jìn)程死鎖了。下面代碼示例演示這個(gè)問題:
#include <iostream> //std::cout
#include <thread> //std::thread
#include <mutex> //std::mutex, std::unique_lock
#include <condition_variable> //std::condition_variable
#include <vector>
//鎖資源1
std::mutex mtx1;
//鎖資源2
std::mutex mtx2;
//線程A的函數(shù)
void taskA()
{
//保證線程A先獲取鎖1
std::lock_guard<std::mutex> lockA(mtx1);
std::cout << "線程A獲取鎖1" << std::endl;
//線程A睡眠2s再獲取鎖2,保證鎖2先被線程B獲取,模擬死鎖問題的發(fā)生
std::this_thread::sleep_for(std::chrono::seconds(2));
//線程A先獲取鎖2
std::lock_guard<std::mutex> lockB(mtx2);
std::cout << "線程A獲取鎖2" << std::endl;
std::cout << "線程A釋放所有鎖資源,結(jié)束運(yùn)行!" << std::endl;
}
//線程B的函數(shù)
void taskB()
{
//線程B先睡眠1s保證線程A先獲取鎖1
std::this_thread::sleep_for(std::chrono::seconds(1));
std::lock_guard<std::mutex> lockB(mtx2);
std::cout << "線程B獲取鎖2" << std::endl;
//線程B嘗試獲取鎖1
std::lock_guard<std::mutex> lockA(mtx1);
std::cout << "線程B獲取鎖1" << std::endl;
std::cout << "線程B釋放所有鎖資源,結(jié)束運(yùn)行!" << std::endl;
}
int main()
{
//創(chuàng)建生產(chǎn)者和消費(fèi)者線程
std::thread t1(taskA);
std::thread t2(taskB);
//main主線程等待所有子線程執(zhí)行完
t1.join();
t2.join();
return 0;
}
輸出:

可以看到,線程A獲取鎖1、線程B獲取鎖2以后,進(jìn)程就不往下繼續(xù)執(zhí)行了,一直等待在這里,如果這是我們碰到的一個(gè)問題場景,我們?nèi)绾闻袛喑鲞@是由于線程間死鎖引起的呢?
打開process Explorer.找到該進(jìn)程,查看線程狀態(tài),發(fā)現(xiàn)線程的cpu利用率為0,那么應(yīng)該不是死循環(huán),應(yīng)該是死鎖了:
?
點(diǎn)擊vs 的全部中斷:查看每一個(gè)線程的函數(shù)執(zhí)行的位置

發(fā)現(xiàn)當(dāng)前線程正在申請鎖的位置,判斷出應(yīng)該是鎖了。

同時(shí)主線程走了等待子線程結(jié)束;

那如果是死循環(huán)的情況呢?,如將線程2加一個(gè)死循環(huán):
#include <iostream> //std::cout
#include <thread> //std::thread
#include <mutex> //std::mutex, std::unique_lock
#include <condition_variable> //std::condition_variable
#include <vector>
//鎖資源1
std::mutex mtx1;
//鎖資源2
std::mutex mtx2;
//線程A的函數(shù)
void taskA()
{
//保證線程A先獲取鎖1
std::lock_guard<std::mutex> lockA(mtx1);
std::cout << "線程A獲取鎖1" << std::endl;
//線程A睡眠2s再獲取鎖2,保證鎖2先被線程B獲取,模擬死鎖問題的發(fā)生
std::this_thread::sleep_for(std::chrono::seconds(2));
//線程A先獲取鎖2
std::lock_guard<std::mutex> lockB(mtx2);
std::cout << "線程A獲取鎖2" << std::endl;
std::cout << "線程A釋放所有鎖資源,結(jié)束運(yùn)行!" << std::endl;
}
//線程B的函數(shù)
void taskB()
{
while (true)
{
}
}
int main()
{
//創(chuàng)建生產(chǎn)者和消費(fèi)者線程
std::thread t1(taskA);
std::thread t2(taskB);
//main主線程等待所有子線程執(zhí)行完
t1.join();
t2.join();
return 0;
}

這時(shí)候工作線程占滿了CPU,我的電腦是8核,因此占滿一個(gè)cpu是12.5%
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
C語言編程動(dòng)態(tài)內(nèi)存分配常見錯(cuò)誤全面分析
這篇文章主要介紹了C語言編程中動(dòng)態(tài)內(nèi)存分配的常見錯(cuò)誤全面分析講解,同樣遇到過C語言動(dòng)態(tài)內(nèi)存分配各種問題的同學(xué)可以借鑒參考下,希望能夠有所幫助2021-10-10
C++實(shí)現(xiàn)簡單的HTTP服務(wù)器
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)簡單的HTTP服務(wù)器的相關(guān)資料,感興趣的朋友可以參考下2016-05-05
CreateCompatibleDC()函數(shù)案例詳解
這篇文章主要介紹了CreateCompatibleDC()函數(shù)案例詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08
簡要對比C語言中三個(gè)用于退出進(jìn)程的函數(shù)
這篇文章主要介紹了C語言中三個(gè)用于退出進(jìn)程的函數(shù)的對比,分別為_exit()函數(shù)和on_exit()函數(shù)以及atexit()函數(shù),需要的朋友可以參考下2015-08-08
C++實(shí)現(xiàn)統(tǒng)計(jì)代碼運(yùn)行時(shí)間的示例詳解
這篇文章主要為大家詳細(xì)介紹了C++一個(gè)有趣的小項(xiàng)目——統(tǒng)計(jì)代碼運(yùn)行時(shí)間,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-05-05
C語言中程序環(huán)境和預(yù)處理的詳細(xì)圖文講解
這篇文章主要給大家介紹了關(guān)于C語言中程序環(huán)境和預(yù)處理的相關(guān)資料,我們寫的C語言代碼,從運(yùn)行,到在屏幕上生成結(jié)果,經(jīng)歷了比較復(fù)雜的過程,需要的朋友可以參考下2023-02-02

