国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - C/C++ - 詳解C語言編程之thread多線程

詳解C語言編程之thread多線程

2022-03-09 14:46~怎么回事啊~ C/C++

這篇文章主要為大家介紹了C語言編程之thread多線程,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助

線程創建與結束

C++11 新標準中引入了四個頭文件來支持多線程編程,他們分別是<atomic> ,<thread>,<mutex>,<condition_variable>和<future>。

  • <atomic>:該頭文主要聲明了兩個類, std::atomic 和 std::atomic_flag,另外還聲明了一套 C 風格的原子類型和與 C 兼容的原子操作的函數。
  • <thread>:該頭文件主要聲明了 std::thread 類,另外 std::this_thread 命名空間也在該頭文件中。
  • <mutex>:該頭文件主要聲明了與互斥量(mutex)相關的類,包括 std::mutex 系列類,std::lock_guard, std::unique_lock, 以及其他的類型和函數。
  • <condition_variable>:該頭文件主要聲明了與條件變量相關的類,包括 std::condition_variable 和 std::condition_variable_any。
  • <future>:該頭文件主要聲明了 std::promise, std::package_task 兩個 Provider 類,以及 std::future 和 std::shared_future 兩個 Future 類,另外還有一些與之相關的類型和函數,std::async() 函數就聲明在此頭文件中。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#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 不是一個線程
  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 這時候t3將不是線程,t4接替t3繼續運行f2
  t2.join();
  t4.join();
  std::cout << "Final value of n is " << n << '\n';
}

線程的創建方式:

(1). 默認構造函數,創建一個空的 thread 執行對象。

(2). 初始化構造函數,創建一個 thread對象,該 thread對象可被 joinable,新產生的線程會調用 fn 函數,該函數的參數由 args 給出。

(3). 拷貝構造函數(被禁用),意味著 thread 不可被拷貝構造。

(4). move 構造函數,move 構造函數,調用成功之后 x 不代表任何 thread 執行對象。

注意:可被 joinable 的 thread 對象必須在他們銷毀之前被主線程 join 或者將其設置為 detached.

std::thread定義一個線程對象,傳入線程所需要的線程函數和參數,線程自動開啟

線程的結束方式:

join()

創建線程執行線程函數,調用該函數會阻塞當前線程,直到線程執行完join才返回;等待t線程結束,當前線程繼續往下運行

detach()

detach調用之后,目標線程就成為了守護線程,駐留后臺運行,與之關聯的std::thread對象失去對目標線程的關聯,無法再通過std::thread對象取得該線程的控制權,由操作系統負責回收資源;主線程結束,整個進程結束,所有子線程都自動結束了!

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#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()
{
    //創建了一個線程對象,傳入一個線程函數(作為線程入口函數),
    //新線程就開始運行了,沒有先后順序,隨著CPU的調度算法執行
    std::thread t1(threadHandle1, 2);
    std::thread t2(threadHandle2, 3);
    //主線程(main)運行到這里,等待子線程結束,主線程才繼續往下運行
    t1.join();
    t2.join();
    //把子線程設置為分離線程,子線程和主線程就毫無關系了
    //主線程結束的時候查看其他線程
    //但是這個子線程運行完還是沒運行完都和這個主線程沒關系了
    //這個子線程就從這個main分離出去了
    //運行程序時也看不到這個子線程的任何輸出打印了
    //t1.detach();
    cout << "main thread done!" << endl;
    //主線程運行完成,查看如果當前進程還有未運行完成的子線程
    //進程就會異常終止
    return 0;
}

互斥鎖

Mutex 又稱互斥量,C++ 11中與 Mutex 相關的類(包括鎖類型)和函數都聲明在 <mutex> 頭文件中,所以如果你需要使用 std::mutex,就必須包含 <mutex> 頭文件。

<mutex> 頭文件介紹

Mutex 系列類(四種)

  • std::mutex,最基本的 Mutex 類。
  • std::recursive_mutex,遞歸 Mutex 類。
  • std::time_mutex,定時 Mutex 類。
  • std::recursive_timed_mutex,定時遞歸 Mutex 類。

Lock 類(兩種)

std::lock_guard,與 Mutex RAII 相關,方便線程對互斥量上鎖。

std::unique_lock,與 Mutex RAII 相關,方便線程對互斥量上鎖,但提供了更好的上鎖和解鎖控制。

其他類型

  • std::once_flag
  • std::adopt_lock_t
  • std::defer_lock_t
  • std::try_to_lock_t

函數

  • std::try_lock,嘗試同時對多個互斥量上鎖。
  • std::lock,可以同時對多個互斥量上鎖。
  • std::call_once,如果多個線程需要同時調用某個函數,call_once 可以保證多個線程對該函數只調用一次。

std::mutex 介紹

下面以 std::mutex 為例介紹 C++11 中的互斥量用法。

std::mutex 是C++11 中最基本的互斥量,std::mutex 對象提供了獨占所有權的特性——即不支持遞歸地對 std::mutex 對象上鎖,而 std::recursive_lock 則可以遞歸地對互斥量對象上鎖。

std::mutex 的成員函數

  • 構造函數,std::mutex不允許拷貝構造,也不允許 move 拷貝,最初產生的 mutex 對象是處于 unlocked 狀態的。
  • lock(),調用線程將鎖住該互斥量。線程調用該函數會發生下面 3 種情況:

(1). 如果該互斥量當前沒有被鎖住,則調用線程將該互斥量鎖住,直到調用 unlock之前,該線程一直擁有該鎖。

(2). 如果當前互斥量被其他線程鎖住,則當前的調用線程被阻塞住。

(3). 如果當前互斥量被當前調用線程鎖住,則會產生死鎖(deadlock)。

  • unlock(), 解鎖,釋放對互斥量的所有權。
  • try_lock(),嘗試鎖住互斥量,如果互斥量被其他線程占有,則當前線程也不會被阻塞。線程調用該函數也會出現下面 3 種情況

(1). 如果當前互斥量沒有被其他線程占有,則該線程鎖住互斥量,直到該線程調用 unlock 釋放互斥量。

(2). 如果當前互斥量被其他線程鎖住,則當前調用線程返回 false,而并不會被阻塞掉。

(3). 如果當前互斥量被當前調用線程鎖住,則會產生死鎖(deadlock)

為了保證lock()和unlock()對應使用,一般不直接使用mutex,而是和lock_guard、unique_lock一起使用;

std::lock_guard

std::lock_guard是RAII模板類的簡單實現,功能簡單。

1.std::lock_guard 在構造函數中進行加鎖,析構函數中進行解鎖。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
        // 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源碼可以看出,它在構造時進行上鎖,在出作用域執行析構函數釋放鎖;同時不允許拷貝構造和賦值運算符;比較簡單,不能用在函數參數傳遞或者返回過程中,因為它的拷貝構造和賦值運算符被禁用了;只能用在簡單的臨界區代碼的互斥操作

std::unique_lock

類 unique_lock 是通用互斥包裝器,允許延遲鎖定、鎖定的有時限嘗試、遞歸鎖定、所有權轉移和與條件變量一同使用。
unique_lock比lock_guard使用更加靈活,功能更加強大。
使用unique_lock需要付出更多的時間、性能成本。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
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; 指向一把鎖的指針;不允許使用左值拷貝構造和賦值,但是可以使用右值拷貝構造和賦值,可以在函數調用過程中使用。因此可以和條件變量一起使用:cv.wait(lock);//可以作為函數參數傳入;

示例:

在多線程環境中運行的代碼段,需要考慮是否存在競態條件,如果存在競態條件,我們就說該代碼段不是線程安全的,不能直接運行在多線程環境當中,對于這樣的代碼段,我們經常稱之為臨界區資源,對于臨界區資源,多線程環境下需要保證它以原子操作執行,要保證臨界區的原子操作,就需要用到線程間的互斥操作-鎖機制,thread類庫還提供了更輕量級的基于CAS操作的原子操作類。

無鎖時:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
?
#include <iostream>
#include <atomic>//C++11線程庫提供的原子類
#include <thread>//C++線程類庫的頭文件
#include <vector>
int count = 0;
//線程函數
void sumTask()
{
  //每個線程給count加10次
  for (int i = 0; i < 10; ++i)
  {
    count++;
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
  }
}
int main()
{
  //創建10個線程放在容器當中
  std::vector<std::thread> vec;
  for (int i = 0; i < 10; ++i)
  {
    vec.push_back(std::thread(sumTask));
  }
  //等待線程執行完成
  for (unsigned int i = 0; i < vec.size(); ++i)
  {
    vec[i].join();
  }
  //所有子線程運行結束
  std::cout << "count : " << count << std::endl;
  return 0;
}
?

多線程同時對count進行操作,并不能保證同時只有一個線程對count執行++操作,最后的的結果不一定是100;

使用lock_guard:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
#include <atomic>//C++11線程庫提供的原子類
#include <thread>//C++線程類庫的頭文件
#include <mutex>
#include <vector>
int count = 0;
std::mutex mutex;
//線程函數
void sumTask()
{
  //每個線程給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()
{
  //創建10個線程放在容器當中
  std::vector<std::thread> vec;
  for (int i = 0; i < 10; ++i)
  {
    vec.push_back(std::thread(sumTask));
  }
  //等待線程執行完成
  for (unsigned int i = 0; i < vec.size(); ++i)
  {
    vec[i].join();
  }
  //所有子線程運行結束,count的結果每次運行應該都是10000
  std::cout << "count : " << count << std::endl;
  return 0;
}

對count++ 操作上鎖,保證一次只有一個線程能對其操作,結果是100

原子變量

上面的保證原子操作需要在多線程環境下添加互斥操作,但是mutex互斥鎖畢竟比較重,對于系統消耗有些大,C++11的thread類庫提供了針對簡單類型的原子操作類,如std::atomic_int,atomic_longatomic_bool等,它們值的增減都是基于CAS操作的,既保證了線程安全,效率還非常高。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <atomic>//C++11線程庫提供的原子類
#include <thread>//C++線程類庫的頭文件
#include <vector>
//原子整型,CAS操作保證給count自增自減的原子操作
std::atomic_int count = 0;
//線程函數
void sumTask()
{
  //每個線程給count加10次
  for (int i = 0; i < 10; ++i)
  {
    count++;
  }
}
int main()
{
  //創建10個線程放在容器當中
  std::vector<std::thread> vec;
  for (int i = 0; i < 10; ++i)
  {
    vec.push_back(std::thread(sumTask));
  }
  //等待線程執行完成
  for (unsigned int i = 0; i < vec.size(); ++i)
  {
    vec[i].join();
  }
  //所有子線程運行結束,count的結果每次運行應該都是10000
  std::cout << "count : " << count << std::endl;
  return 0;
}

線程同步通信

多線程在運行過程中,各個線程都是隨著OS的調度算法,占用CPU時間片來執行指令做事情,每個線程的運行完全沒有順序可言。但是在某些應用場景下,一個線程需要等待另外一個線程的運行結果,才能繼續往下執行,這就需要涉及線程之間的同步通信機制。

線程間同步通信最典型的例子就是生產者-消費者模型,生產者線程生產出產品以后,會通知消費者線程去消費產品;如果消費者線程去消費產品,發現還沒有產品生產出來,它需要通知生產者線程趕快生產產品,等生產者線程生產出產品以后,消費者線程才能繼續往下執行。

C++11 線程庫提供的條件變量condition_variable,就是Linux平臺下的Condition Variable機制,用于解決線程間的同步通信問題,下面通過代碼演示一個生產者-消費者線程模型:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#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容器,作為生產者和消費者共享的容器
std::vector<int> vec;
//生產者線程函數
void producer()
{
  //生產者每生產一個,就通知消費者消費一個
  for (int i = 1; i <= 10; ++i)
  {
    //獲取mtx互斥鎖資源
    std::unique_lock<std::mutex> lock(mtx);
    //如果容器不為空,代表還有產品未消費,等待消費者線程消費完,再生產
    while (!vec.empty())
    {
      //判斷容器不為空,進入等待條件變量的狀態,釋放mtx鎖,
      //讓消費者線程搶到鎖能夠去消費產品
      cv.wait(lock);
    }
    vec.push_back(i); // 表示生產者生產的產品序號i
    std::cout << "producer生產產品:" << i << std::endl;
    /*
    生產者線程生產完產品,通知等待在cv條件變量上的消費者線程,
    可以開始消費產品了,然后釋放鎖mtx
    */
    cv.notify_all();
    //生產一個產品,睡眠100ms
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
  }
}
//消費者線程函數
void consumer()
{
  //消費者每消費一個,就通知生產者生產一個
  for (int i = 1; i <= 10; ++i)
  {
    //獲取mtx互斥鎖資源
    std::unique_lock<std::mutex> lock(mtx);
    //如果容器為空,代表還有沒有產品可消費,等待生產者生產,再消費
    while (vec.empty())
    {
      //判斷容器為空,進入等待條件變量的狀態,釋放mtx鎖,
      //讓生產者線程搶到鎖能夠去生產產品
      cv.wait(lock);
    }
    int data = vec.back(); // 表示消費者消費的產品序號i
    vec.pop_back();
    std::cout << "consumer消費產品:" << data << std::endl;
    /*
    消費者消費完產品,通知等待在cv條件變量上的生產者線程,
    可以開始生產產品了,然后釋放鎖mtx
    */
    cv.notify_all();
    //消費一個產品,睡眠100ms
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
  }
}
int main()
{
  //創建生產者和消費者線程
  std::thread t1(producer);
  std::thread t2(consumer);
  //main主線程等待所有子線程執行完
  t1.join();
  t2.join();
  return 0;
}

線程死鎖

死鎖概述

線程死鎖是指兩個或兩個以上的線程互相持有對方所需要的資源,由于synchronized的特性,一個線程持有一個資源,或者說獲得一個鎖,在該線程釋放這個鎖之前,其它線程是獲取不到這個鎖的,而且會一直死等下去,因此這便造成了死鎖。

死鎖產生的條件

  • 互斥條件:一個資源,或者說一個鎖只能被一個線程所占用,當一個線程首先獲取到這個鎖之后,在該線程釋放這個鎖之前,其它線程均是無法獲取到這個鎖的。
  • 占有且等待:一個線程已經獲取到一個鎖,再獲取另一個鎖的過程中,即使獲取不到也不會釋放已經獲得的鎖。
  • 不可剝奪條件:任何一個線程都無法強制獲取別的線程已經占有的鎖
  • 循環等待條件:線程A拿著線程B的鎖,線程B拿著線程A的鎖。

示例:

當一個程序的多個線程獲取多個互斥鎖資源的時候,就有可能發生死鎖問題,比如線程A先獲取了鎖1,線程B獲取了鎖2,進而線程A還需要獲取鎖2才能繼續執行,但是由于鎖2被線程B持有還沒有釋放,線程A為了等待鎖2資源就阻塞了;線程B這時候需要獲取鎖1才能往下執行,但是由于鎖1被線程A持有,導致A也進入阻塞。

線程A和線程B都在等待對方釋放鎖資源,但是它們又不肯釋放原來的鎖資源,導致線程A和B一直互相等待,進程死鎖了。下面代碼示例演示這個問題:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#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的函數
void taskA()
{
  //保證線程A先獲取鎖1
  std::lock_guard<std::mutex> lockA(mtx1);
  std::cout << "線程A獲取鎖1" << std::endl;
  //線程A睡眠2s再獲取鎖2,保證鎖2先被線程B獲取,模擬死鎖問題的發生
  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釋放所有鎖資源,結束運行!" << std::endl;
}
//線程B的函數
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釋放所有鎖資源,結束運行!" << std::endl;
}
int main()
{
  //創建生產者和消費者線程
  std::thread t1(taskA);
  std::thread t2(taskB);
  //main主線程等待所有子線程執行完
  t1.join();
  t2.join();
  return 0;
}

輸出:

詳解C語言編程之thread多線程

可以看到,線程A獲取鎖1、線程B獲取鎖2以后,進程就不往下繼續執行了,一直等待在這里,如果這是我們碰到的一個問題場景,我們如何判斷出這是由于線程間死鎖引起的呢?

打開process Explorer.找到該進程,查看線程狀態,發現線程的cpu利用率為0,那么應該不是死循環,應該是死鎖了:

詳解C語言編程之thread多線程

點擊vs 的全部中斷:查看每一個線程的函數執行的位置

詳解C語言編程之thread多線程

發現當前線程正在申請鎖的位置,判斷出應該是鎖了。

詳解C語言編程之thread多線程

同時主線程走了等待子線程結束;

詳解C語言編程之thread多線程

那如果是死循環的情況呢?,如將線程2加一個死循環:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#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的函數
void taskA()
{
  //保證線程A先獲取鎖1
  std::lock_guard<std::mutex> lockA(mtx1);
  std::cout << "線程A獲取鎖1" << std::endl;
  //線程A睡眠2s再獲取鎖2,保證鎖2先被線程B獲取,模擬死鎖問題的發生
  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釋放所有鎖資源,結束運行!" << std::endl;
}
//線程B的函數
void taskB()
{
  while (true)
  {
  }
}
int main()
{
  //創建生產者和消費者線程
  std::thread t1(taskA);
  std::thread t2(taskB);
  //main主線程等待所有子線程執行完
  t1.join();
  t2.join();
  return 0;
}

詳解C語言編程之thread多線程

這時候工作線程占滿了CPU,我的電腦是8核,因此占滿一個cpu是12.5%

總結

本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關注服務器之家的更多內容!

原文鏈接:https://blog.csdn.net/LIJIWEI0611/article/details/121717066

延伸 · 閱讀

精彩推薦
  • C/C++c++ 單線程實現同時監聽多個端口

    c++ 單線程實現同時監聽多個端口

    這篇文章主要介紹了c++ 單線程實現同時監聽多個端口的方法,幫助大家更好的理解和學習使用c++,感興趣的朋友可以了解下...

    源之緣11542021-10-27
  • C/C++詳解c語言中的 strcpy和strncpy字符串函數使用

    詳解c語言中的 strcpy和strncpy字符串函數使用

    strcpy 和strcnpy函數是字符串復制函數。接下來通過本文給大家介紹c語言中的strcpy和strncpy字符串函數使用,感興趣的朋友跟隨小編要求看看吧...

    spring-go5642021-07-02
  • C/C++C語言中炫酷的文件操作實例詳解

    C語言中炫酷的文件操作實例詳解

    內存中的數據都是暫時的,當程序結束時,它們都將丟失,為了永久性的保存大量的數據,C語言提供了對文件的操作,這篇文章主要給大家介紹了關于C語言中文件...

    針眼_6702022-01-24
  • C/C++C++之重載 重定義與重寫用法詳解

    C++之重載 重定義與重寫用法詳解

    這篇文章主要介紹了C++之重載 重定義與重寫用法詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下...

    青山的青6062022-01-04
  • C/C++深入理解goto語句的替代實現方式分析

    深入理解goto語句的替代實現方式分析

    本篇文章是對goto語句的替代實現方式進行了詳細的分析介紹,需要的朋友參考下...

    C語言教程網7342020-12-03
  • C/C++C語言實現電腦關機程序

    C語言實現電腦關機程序

    這篇文章主要為大家詳細介紹了C語言實現電腦關機程序,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下...

    xiaocaidayong8482021-08-20
  • C/C++C/C++經典實例之模擬計算器示例代碼

    C/C++經典實例之模擬計算器示例代碼

    最近在看到的一個需求,本以為比較簡單,但花了不少時間,所以下面這篇文章主要給大家介紹了關于C/C++經典實例之模擬計算器的相關資料,文中通過示...

    jia150610152021-06-07
  • C/C++學習C++編程的必備軟件

    學習C++編程的必備軟件

    本文給大家分享的是作者在學習使用C++進行編程的時候所用到的一些常用的軟件,這里推薦給大家...

    謝恩銘10102021-05-08
主站蜘蛛池模板: 欧美日韩在线视频免费 | 亚洲国产精品久久久 | 亚洲欧洲精品视频在线观看 | 精品久久久久一区二区国产 | 在线一区二区三区 | 天堂资源 | 天天操夜夜操av | 韩日在线| 久久91久久久久麻豆精品 | 福利成人 | 精品少妇一区二区三区日产乱码 | 亚洲高清电影 | 免费啪啪av乱一区 | 国产视频一区在线 | 老色批影院| 亚洲综合射 | 亚洲精品视频国产 | 国产精品日本欧美一区二区三区 | 国产精品毛片久久久久久久 | 国产精品二区三区 | 精品无码久久久久久国产 | bxbx成人精品一区二区三区 | 亚洲精品一 | 在线中文字幕视频 | 中文色视频 | 91精品久久久久久久久久入口 | 国产成人精品综合 | 久久精品亚洲精品国产欧美kt∨ | 久久伊人中文字幕 | 日韩三级网址 | 国产精品高潮呻吟久久av野狼 | 亚洲wu码| 亚洲在线视频 | 成人网av | 桃色视频在线播放 | 国产精品色一区二区三区 | 欧美午夜寂寞影院 | 国产精品suv一区二区 | 免费污视频在线 | 中文二区| 久久av网|