程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> C++多線程編程

C++多線程編程

編輯:關於C++

概述

OS:Raspbian

什麼叫做多線程編程,大概意思就是說對多個任務同時進行控制,而且相互之間還需要協調。相信很多人和我一樣在開始的時候,認為多線程編程是為了利用多核處理器,使程序運行的更快。但是,現在只要打開操作系統,那麼肯定就不止一個任務在工作,所以,和多核沒有什麼關系。加快速度,從某種層面上來說的確有這個效果。但是,其作用的方式是不同的,有點懵?OK,我們一會再說。

在項目中,遇到了不少需要用到多線程編程的例子。有的是在C++庫裡需要用到,有的是在Java語言用到,包括開發安卓的過程中。像是在QT中,用到的庫是QThread,在java中繼承Thread類。在這裡,我想用C++中的thread.h這個頭文件裡的函數來進行舉例,這個頭文件的方便移植且不需要增加其他的庫。雖然,我目前實在Raspbian上面進行編程,但是可以保證在Linux平台下也可以正常運行,windows下mingw也是可以正常運行的,其他的自己試一下吧。

現在,先舉幾個在項目中用到多線程編程的例子吧。
1.armLinux的開發中,經常使用中斷。(什麼叫中斷模式,百度百科:暫時停止當前程序的執行轉而執行處理新情況的程序和執行過程。簡單的來說:就是當運行到某處,暫停運行,直到從硬件處獲得某個信號,比如說獲得鍵盤某個鍵按下,以後有機會細講)這時候,我們不可以讓主進程暫停了,因為主進程還有其他操作。有時候,用到的中斷程序不止一個。那麼,就需要開多個線程每個中斷都處於不同的線程當中,才能檢測各個中斷信號。
2.在開發安卓的過程中,有一些操作是不允許在主線程中進行操作。例如網絡通信。其實也是可以理解的,網絡通信大多都需要長時間操作,那麼如果主線程已經在操作UI了,如果此時耗時太多在網絡通信上面的話,UI的創建就會延遲,這樣手機就會出現卡頓,我們的手機已經夠卡的了,所以,安卓就不允許在主線程中進行網絡通信。

多線程基本使用

創建兩個線程,輸出他們是第幾個線程。

#include 
// 線程相關類,在類Unix下一般都有
#include 

using namespace std;
// 定義線程啟動的函數
void* fun1(void* args)
{
    cout << "This is first Thread!\n";
}

void* fun2(void* args)
{
    cout << "This is second Thread!\n";
}

int main()
{
    // 線程結構體
    pthread_t pt1,pt2;
    // 創建線程,線程創建成功,返回值是0
    if(pthread_create(&pt1,NULL,fun1,NULL) != 0)
    {
        cout << "Error in create first Thread!\n";
    }
    if(pthread_create(&pt2,NULL,fun2,NULL) != 0)
    {
        cout << "Error in create second Thread!\n";
    }
    //等待線程結束後,結束進程
    pthread_exit(NULL);
    return 0;
}

注意:在這裡編譯的時候,需要在IDE中設置靜態編譯庫。所以,我就直接用命令行來進行編譯。

g++ -o thread0 thread0.cpp -lpthread
./thread0

大多數情況下會先輸出第一個線程所要輸出的內容,但是如果多次運行的話,會發現有時候線程二的內容會在線程一的內容出來前出現。OK,現在有三個問題了:
1.不是C++多線程編程嗎,那麼怎麼在類裡面使用呢?
2.怎麼往線程裡面傳入參數呢?
3.如果我們必須要線程一完成以後再進行線程二,怎麼辦?

線程調用類中函數

如果線程調用類中的函數,那麼必須將該函數生命為靜態函數。
靜態成員函數屬於靜態全局區,線程可以共享該區域。

#include 
// 線程相關類,在類Unix下一般都有
#include 

using namespace std;

// 定義一個類
class MyThread
{
private:
public:
    // 定義線程啟動的函數,必須是靜態的
    static void* fun1(void* args);
    static void* fun2(void* args);
};

void* MyThread::fun1(void* args)
{
    cout << "This is first Thread!\n";
}

void* MyThread::fun2(void* args)
{
    cout << "This is second Thread!\n";
}

int main()
{
    // 線程結構體
    pthread_t pt1,pt2;
    // 創建線程,線程創建成功,返回值是0
    if(pthread_create(&pt1,NULL,MyThread::fun1,NULL) != 0)
    {
        cout << "Error in create first Thread!\n";
    }
    if(pthread_create(&pt2,NULL,MyThread::fun2,NULL) != 0)
    {
        cout << "Error in create second Thread!\n";
    }
    //等待線程結束後,結束進程
    pthread_exit(NULL);
    return 0;
}

命令其實和上面一樣的:

g++ -o thread1 thread1.cpp -lpthread
./thread1

傳入參數

現在,往裡面傳入參數來確定分別是哪個線程來調用的。

#include 
// 線程相關類,在類Unix下一般都有
#include 
#include 

using namespace std;

// 定義一個類
class MyThread
{
public:
    // 定義線程啟動的函數,必須是靜態的
    static void* fun(void* args);
};

void* MyThread::fun(void* args)
{
    cout << "This is " << *((string*) args) << " Thread!\n";
}

int main()
{
    // 線程結構體
    pthread_t pt;
    string ss1,ss2;
    // 創建線程,線程創建成功,返回值是0
    ss1 = "first";
    if(pthread_create(&pt,NULL,MyThread::fun,(void*)&ss1) != 0)
    {
        cout << "Error in create first Thread!\n";
    }
    ss2 = "second";
    if(pthread_create(&pt,NULL,MyThread::fun,(void*)&ss2) != 0)
    {
        cout << "Error in create first Thread!\n";
    }
    //等待線程結束後,結束進程
    pthread_exit(NULL);
    return 0;
}

編譯運行命令以後不寫了,同上即可。
我們來看一下結果,結果有點奇怪。有時候是first在前,有時候second在前,這自然不用多說。但是還有一種情況是:段錯誤。所以,我們繼續來控制一下每個線程運行的前後順序。

線程控制

結束某個線程或者控制線程等待某個信號量

定義兩個線程,兩個線程啟動,在第一次輸入後,結束地一個線程,在第二次輸入後,開始運行線程二中的實際內容。

#include 
#include 
#include 

using namespace std;

// 定義第一個線程類
class MyThread0
{
public:
    static bool run;
    // 定義線程啟動的函數,必須是靜態的
    static void* fun(void* args);
};

// 定義第二個線程類
class MyThread1
{
public:
    static bool waitRun;
    // 定義線程啟動的函數,必須是靜態的
    static void* fun(void* args);
};

bool MyThread0::run = true;
bool MyThread1::waitRun = true;

void* MyThread0::fun(void* args)
{
    int i = 0;
    while(run)
    {
        i = 1;
        // 執行的任務
    }
    cout << i << endl;
}

void* MyThread1::fun(void* args)
{
    while(waitRun);
    // 實際執行的任務
    cout << "MyThread1 is OK!" << endl;
}

int main()
{
    pthread_t pt;
    if(pthread_create(&pt,NULL,MyThread0::fun,NULL) != 0)
    {
        cout << "Error in create first Thread!\n";
    }
    if(pthread_create(&pt,NULL,MyThread1::fun,NULL) != 0)
    {
        cout << "Error in create first Thread!\n";
    }
    // 至此兩個程序已經開始運行
    getchar();
    MyThread0::run = false;
    getchar();
    MyThread1::waitRun = false;
    pthread_exit(NULL);
    return 0;
}

我用這個方法很長時間了,殺人越貨之利器,好理解,沒有額外的函數,而且無論語言都可以直接移植。但是畢竟不是個特別好的辦法,而且如果兩個線程共享一個數據,那麼就一定要先鎖定某個線程,然後准備好資源後才可以啟動另外一個線程,太過麻煩。而且一直要陷在死循環裡暫停程序也不像個事。(其實,我是從嵌入式裡面學到的)其實,在學習操作系統/數據庫原理中對於並發性操作,都是相當需要注意的,每個線程的先後都要控制好,而且需要注意數據的保護和讀寫,這時候我們就需要引入互斥鎖機制。

等待某個線程結束再開始其他線程

我們首先來看一下程序,程序中最後有個函數pthread_exit,這裡的意思是等所有子線程結束後,主線程結束,也就是說到這個函數程序將會結束,以後所有的語句都不會再執行。而且,還有個問題就是等待所有線程結束,根本無法判斷個別線程結束。那麼,我們可不可以先等待某個線程結束,再繼續運行下一步呢?

#include 
#include 
#include 

using namespace std;

void* change_value(void* args)
{
    cout << "This thread value is " << *((int *)args) <<  endl;
}

int main()
{
    int value1 = 1,value2 = 2;
    pthread_t pt1,pt2;
    int ret = pthread_create(&pt1, NULL, change_value, (void*)&value1);
    if(ret != 0)
    {
    cout << "pthread_create error:error_code=" << ret << endl;
    }
    //pthread_join的另外一個作用就是可以控制等待某個進程結束
    pthread_join(pt1,NULL);
    ret = pthread_create(&pt2, NULL, change_value, (void*)&value2);
    if(ret != 0)
    {
    cout << "pthread_create error:error_code=" << ret << endl;
    }
    pthread_join(pt2,NULL);
    return 0;
}

獲得線程結束信息

我們首先來看一下程序,程序中最後有個函數pthread_exit,這裡的意思是等子線程結束後,主線程結束,也就是說到這個函數程序將會結束,以後所有的語句都不會再執行。OK,那麼我們即使獲得線程結束信息,也無法表示。那麼我們吧這條語句去了,去了的話會發現有的線程還沒有運行完成,程序就已經結束了,怎麼解決?當然也可以估計一下時間然後等待,或者設置一個公共變量,然後在主線程中利用循環暫停,但是,我們這裡用系統提供給我們的函數來進行操作。

#include 
#include 
#include 

using namespace std;

void* change_value(void* args)
{
    cout << "This thread value is " << *((int *)args) <<  endl;
    int result = *((int *)args) + 10;
    pthread_exit((void*)result);
}

int main()
{
    int value1 = 1,value2 = 3;
    void* result;
    pthread_t pt1,pt2;
    int ret = pthread_create(&pt1, NULL, change_value, (void*)&value1);
    if(ret != 0)
    {
        cout << "pthread_create error:error_code=" << ret << endl;
    }
    pthread_join(pt1,&result);
    cout << "The thread1's result is " << (int)result << endl;
    ret = pthread_create(&pt2, NULL, change_value, (void*)&value2);
    if(ret != 0)
    {
        cout << "pthread_create error:error_code=" << ret << endl;
    }
    pthread_join(pt2,&result);
    cout << "The thread2's result is " << (int)result << endl;
    return 0;
}

互斥鎖

互斥鎖的作用是訪問數據時可以保證是被一個線程所訪問,並不能控制線程執行順序的先後。

#include 
#include 

using namespace std;

// 定義第一個線程類
class MyThread
{
public:
    static int flag;
    // 定義線程啟動的函數,必須是靜態的
    static void* fun1(void* args);
    static void* fun2(void* args);
};

int MyThread::flag = 0;
pthread_mutex_t sum_mutex;
int temp;

void* MyThread::fun1(void* args)
{
    // 上鎖
    pthread_mutex_lock(&sum_mutex);
    cout << "This is " << ++flag << " Thread1!\n";
    // 解鎖
    pthread_mutex_unlock(&sum_mutex);
}

void* MyThread::fun2(void* args)
{
    pthread_mutex_lock(&sum_mutex);
    cout << "This is " << ++flag << " Thread2!\n";
    pthread_mutex_unlock(&sum_mutex);
}

int main()
{
    pthread_t pt;
    if(pthread_create(&pt,NULL,MyThread::fun1,NULL) != 0)
    {
        cout << "Error in create first Thread!\n";
    }
    if(pthread_create(&pt,NULL,MyThread::fun2,NULL) != 0)
    {
        cout << "Error in create first Thread!\n";
    }
    pthread_exit(NULL);
    cout << temp << endl;
    pthread_mutex_destroy(&sum_mutex);
    return 0;
}

基本到此為止,多線程的一些最基本的使用就到此結束了,在使用的過程中,發現自己的很多基礎知識都有很多斷層,大概這就是我用到哪裡學到哪裡的一個大BUG。這樣也好,從最基本的開始一點一點來檢查。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved