1. 程式人生 > >理解c++多執行緒程式設計

理解c++多執行緒程式設計

多執行緒程式設計

  • 本篇博文不是主要介紹互斥鎖之類的,是理解執行緒的執行,以便以後有把握的寫多執行緒程式。
#include<thread>
#include<iostream>
#include <mutex>
std::mutex mutex;
void fun1()
{
	for (int i = 0; i <5 ; ++i) {
       std::cout<<"<fun1-1>\n";
   }
}
void fun2(){
     for (int i = 0; i < 5; ++i) {
         std:
:cout<<"<fun2-2>\n"; } } int main() { std::thread th(fun1); std::thread t2(fun2); th.join(); t2.join(); return 0; }
  • 結果:

按照我們的想象,坑定是先列印很多<fun1-1>,再列印<fun2-2> 然而,你會發現每次執行結果都不一樣,而且<fun1-1>與<fun2-2>交替輸出。 這是為什麼呢?以前學過作業系統,知道執行緒會競爭資源,我們這裡可以把cout輸出看作是一種資源,他們都要輸出一段字串,那麼必定要競爭輸出資源,每次誰得到這個資源,就列印自己的輸出,所以就會得到這種輸出現象。

  • 怎麼解決這種問題?--------加鎖

作業系統學過,防止資源競爭造成的死鎖,一般可以通過加互斥鎖避免。

#include<thread>
#include<iostream>
#include <mutex>
std::mutex mutex;
void fun1()
{
    {
        std::unique_lock<std::mutex> lock(mutex);
        for (int i = 0; i <5 ; ++i) {
            std::cout<<"<fun1>\n"
; } } } void fun2(){ { std::unique_lock<std::mutex> lock(mutex); for (int i = 0; i < 5; ++i) { std::cout<<"<fun2>\n"; } } } int main() { std::thread th(fun1); std::thread t2(fun2); th.join(); t2.join(); return 0; }
  • 這樣結果就和我們一開始的期望一樣了。

進一步思考

  • 加鎖也只是針對加鎖的那一個作用域,如果,我們在作用域外,再新增列印任務,會這麼輸出?如何解釋這種現象?
#include<thread>
#include<iostream>
#include <mutex>
std::mutex mutex;
void fun1()
{
    {//程式碼塊1
        std::unique_lock<std::mutex> lock(mutex);
        for (int i = 0; i <5 ; ++i) {
            std::cout<<"<fun1>\n";
        }
    }

    for (int i = 0; i <5 ; ++i) {//程式碼塊2
        std::cout<<"<fun1-1>\n";
    }

}
void fun2(){

    {//程式碼塊3
        std::unique_lock<std::mutex> lock(mutex);
        for (int i = 0; i < 5; ++i) {
            std::cout<<"<fun2>\n";
        }
    }
    for (int i = 0; i < 5; ++i) {//程式碼塊4
        std::cout<<"<fun2-2>\n";
    }

}

int main()
{
    std::thread th(fun1);
    std::thread t2(fun2);
    th.join();
    t2.join();
    return 0;
}

  • 先猜一下,程式碼塊1和3加鎖了,所以輸出完5個<fun1>才會輸出<fun2>
  • 結果如下:
<fun1>
<fun1>
<fun1>
<fun1>
<fun1>
<fun2>
<fun2>
<fun1-1>
<fun2>
<fun1-1>
<fun1-1>
<fun2>
<fun1-1>
<fun2>
<fun1-1>
<fun2-2>
<fun2-2>
<fun2-2>
<fun2-2>
<fun2-2>
  • 1.可以看到我們的預期是對的。
  • 2.從結果來看<fun1-1>與<fun2>交替輸出,<fun2-2>最後輸出(這點有問題,馬上解釋為什麼)
  • 那為什麼會這樣呢?
  • 解釋:

1)當建立好了2個執行緒th和t2,它們準備開始執行函式。 2)它們都進入函式體發現程式碼塊1和程式碼塊3加了互斥鎖,那這樣的話只能讓程式碼塊1執行完,然後釋放鎖,程式碼塊3才能獲得鎖,它才能開始執行。所以我們預期的結果就產生了。 3)但是當代碼塊1執行完了,程式碼塊2並沒有加鎖,這時它就和程式碼塊3與程式碼4形成了競爭關係,所以會產生<fun1-1>與<fun2>交替輸出的現象,理論上也可能產生<fun1-1>與<fun2-2>交替輸出(所以最後輸出也可能是<fun1-1>與<fun2-2>交替輸出)。 4)但是函式執行是順序的,也就是說程式碼塊2一定在程式碼塊1後執行、程式碼塊4一定在程式碼塊3前執行,也就是說不可能出現<fun1-1>在<fun1>前輸出、<fun2-2>不可能出現在<fun2>之前。

  • 改一下程式碼驗證一下。
#include<thread>
#include<iostream>
#include <mutex>
std::mutex mutex;
void fun1()
{
    {
        std::unique_lock<std::mutex> lock(mutex);
        for (int i = 0; i <5 ; ++i) {
            std::cout<<"<fun1>\n";
        }
    }

    for (int i = 0; i <10 ; ++i) {
        std::cout<<"<fun1-1>\n";
    }

}
void fun2(){

    {
        std::unique_lock<std::mutex> lock(mutex);
        for (int i = 0; i < 5; ++i) {
            std::cout<<"<fun2>\n";
        }
    }
    for (int i = 0; i < 5; ++i) {
        std::cout<<"<fun2-2>\n";
    }

}

int main()
{
    std::thread th(fun1);
    std::thread t2(fun2);
    th.join();
    t2.join();
    return 0;
}

  • 結果:
<fun1>
<fun1>
<fun1>
<fun1>
<fun1>
<fun1-1>
<fun2>
<fun2>
<fun1-1>
<fun2>
<fun1-1>
<fun2>
<fun1-1>
<fun2>
<fun2-2>
<fun1-1>
<fun2-2>
<fun1-1>
<fun2-2>
<fun1-1>
<fun2-2>
<fun1-1>
<fun2-2>
<fun1-1>
<fun1-1>

  • 跟我前面的分析完全一致~

小結

  • 深入的理解了這個執行緒的工作機制,也涉及了函式的執行順序,互斥鎖的理解,這樣對以後寫多執行緒程式相信會有很大的好處。