Home Item 8(1/2) - 多線程支持的增強(中文)
Post
Cancel

Item 8(1/2) - 多線程支持的增強(中文)

多執行序

在 C++11 之前,C++ 並沒有標準化的多線程支持,因此需要依賴於平台特定的庫來實現多線程。 簡單來說,就是很麻煩。無法達到跨平台的效果,

例如:

在 Windows 上可以使用 WinAPI, 必須包含 <windows.h> 標頭檔,不需要額外安裝庫但僅適用於 Windows 系統。

而在 POSIX 系統(如 Linux 和 macOS)上則可以使用 POSIX Threads(pthread), 必須包含 <pthread.h> 標頭檔,POSIX Threads 是跨平台的,但需要安裝相關庫。

C++11 引入了一些新的標準庫來增強對多線程的支持,這些新特性使得編寫多線程程序變得更加簡單和安全。

多執行序 code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <thread>

void print_message(const std::string& message) {
    std::cout << message << std::endl;
}

int main() {
    std::thread t1(print_message, "Hello from thread 1!");
    std::thread t2(print_message, "Hello from thread 2!");

    t1.join(); // 等待 t1 執行完畢
    t2.join(); // 等待 t2 執行完畢

    return 0;
}

執行結果:

Desktop View

Desktop View

由於 thread 的執行順序無法控制,以上述的例子就是無法確定誰會先執行 print_message function 因此兩次的執行結果都不一樣。

code with lock_guard

由於 thread 的執行順序,無法被確定。 而某些 code 我們希望一次只能有一個 thread 去執行,譬如 code 當中有個共同變數, 多個 thread 同時去存取易發生不可預期的資料錯誤。

這時候我們就需要使用 std::mutexstd::lock_guard,防止數據競爭。

以下我們開 5 個 thread, 去執行 print_message, 讓他們去等待直到搶到 lock 的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
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>

    std::mutex mtx;

    void print_message(int id) {
        std::cout << "Thread " << id << " is waiting." << std::endl;

        std::lock_guard<std::mutex> lock(mtx);

        std::cout << "Thread " << id << " is running." << std::endl;

        std::this_thread::sleep_for(std::chrono::milliseconds(1000));

        std::cout << "Thread " << id << " has finished." << std::endl;
    }

    int main() {
        std::vector<std::thread> threads;

        for (int i = 0; i < 5; ++i) {
            threads.push_back(std::thread(print_message, i));
        }

        for (auto& t : threads) {
            t.join();
        }

        return 0;
    }

執行結果

Desktop View

解釋一下執行順序:

  1. t0(thread 0) 等待
  2. t0 搶到 lock 執行
  3. t3 等待
  4. t2 & t4 都在等待
  5. t1 等待
  6. t0 完成,釋放 lock
  7. t3 搶到 lock 執行
  8. t3 完成,釋放 lock
  9. t4 搶到 lock 執行
  10. t4 完成,釋放 lock
  11. t2 搶到 lock 執行
  12. t2 完成,釋放 lock
  13. t1 搶到 lock 執行
  14. t1 完成,釋放 lock

code with unique_lock

接下來我們希望可以減少 thread 的等待時間,讓整體效率更好。

std::unique_lock,可以手動解鎖和上鎖。

讓可以被多個 thread 執行的部分繼續執行,執行完繼續上鎖。

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
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>

    std::mutex mtx;

    void print_message(int id) {
        std::cout << "Thread " << id << " is waiting." << std::endl;

        std::unique_lock<std::mutex> lock(mtx);
        std::cout << "Thread " << id << " is running." << std::endl;
        // 可以手動解鎖
        lock.unlock();

        // 執行一些不需要鎖保護的工作
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));

        // 重新上鎖
        lock.lock();
        std::cout << "Thread " << id << " has finished." << std::endl;
    }

    int main() {
        std::vector<std::thread> threads;

        for (int i = 0; i < 5; ++i) {
            threads.push_back(std::thread(print_message, i));
        }

        for (auto& t : threads) {
            t.join();
        }

        return 0;
    }

執行結果

Desktop View

解釋一下執行順序:

  1. t0 等待
  2. t1 等待
  3. t1 搶到 lock 執行
  4. t3 等待
  5. t4 等待
  6. t2 等待
  7. t0 搶到 lock 執行
  8. t3 搶到 lock 執行
  9. t4 搶到 lock 執行
  10. t2 搶到 lock 執行
  11. t3 完成,釋放 lock
  12. t0 完成,釋放 lock
  13. t2 完成,釋放 lock
  14. t1 完成,釋放 lock
  15. t4 完成,釋放 lock

鎖的範圍

鎖的範圍由 std::unique_lock or std::lock_guard 變量的作用域決定。當 std::unique_lock 變量進入作用域時,它會嘗試鎖定給定的 std::mutex,並在該變量離開作用域時自動解鎖。

為何離開作用域會自動解鎖 這是因為 std::unique_lock or std::lock_guard 遵循 RAII(Resource Acquisition Is Initialization)原則。RAII 是 C++ 的一種常見編程習慣,確保資源(如鎖、文件句柄、內存等)在其生命週期內被正確地分配和釋放。

std::unique_lock or std::lock_guard 在其析構函數中會自動調用 unlock() 方法來釋放鎖。因此,當 std::unique_lock or std::lock_guard 對象離開其作用域時(例如函數返回或者作用域結束),析構函數被調用,鎖就會被自動釋放。

使用時機

std::thread:當需要並行執行多個任務時,可以使用 std::thread 創建新線程。這在需要提高程序性能,充分利用多核處理器時特別有用。

std::mutex 和 std::lock_guard/std::unique_lock:當多個線程需要訪問共享資源時,使用 std::mutex 來保護這些資源,防止數據競爭。

☝ツ☝

This post is licensed under CC BY 4.0 by the author.

👈 ツ 👍

Item 8(1/2) - Enhanced Multithreading Support(English)

Item 8 (2/2)- 多線程支持的增強(中文)