国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

這篇具有很好參考價(jià)值的文章主要介紹了『Linux』第九講:Linux多線程詳解(二)_ 線程控制。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

「前言」文章是關(guān)于Linux多線程方面的知識(shí),上一篇是 Linux多線程詳解(一),今天這篇是 Linux多線程詳解(二),講解會(huì)比較細(xì),下面開始!

「歸屬專欄」Linux系統(tǒng)編程

「主頁(yè)鏈接」個(gè)人主頁(yè)

「筆者」楓葉先生(fy)

「楓葉先生有點(diǎn)文青病」「每篇一句」

縱有千古,橫有八荒;

前途似海,來日方長(zhǎng)。
——梁?jiǎn)⒊?/span>

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

目錄

三、 Linux線程控制

3.1?POSIX線程庫(kù)

3.2?線程創(chuàng)建

3.3?線程終止

3.4?線程等待

3.5 線程分離

3.6 重新認(rèn)識(shí)pthread庫(kù)

3.7 封裝線程


三、 Linux線程控制

3.1?POSIX線程庫(kù)

前面我們使用的 pthread線程庫(kù)是歸屬 POSIX線程庫(kù),pthread線程庫(kù)是?POSIX線程庫(kù)的一部分,POSIX線程庫(kù)也叫原生線程庫(kù),遵守 POSIX標(biāo)準(zhǔn):

  • 與線程有關(guān)的函數(shù)構(gòu)成了一個(gè)完整的系列,絕大多數(shù)函數(shù)的名字都是以“pthread_”打頭的
  • 要使用這些函數(shù)庫(kù),要通過引入頭文<pthread.h>
  • 鏈接這些線程函數(shù)庫(kù)時(shí)要使用編譯器命令的 “-lpthread” 選項(xiàng)

POSIX線程庫(kù)錯(cuò)誤檢查:

  • 傳統(tǒng)的一些函數(shù)是,成功返回0,失敗返回-1,并且對(duì)全局變量errno賦值以指示錯(cuò)誤。
  • pthreads函數(shù)出錯(cuò)時(shí)不會(huì)設(shè)置全局變量errno(而大部分其他POSIX函數(shù)會(huì)這樣做),而是將錯(cuò)誤代碼通過返回值返回
  • pthreads同樣也提供了線程內(nèi)的 errno 變量,以支持其它使用 errno 的代碼。對(duì)于 pthreads函數(shù)的錯(cuò)誤,建議通過返回值業(yè)判定,因?yàn)樽x取返回值要比讀取線程內(nèi)的errno變量的開銷更小

3.2?線程創(chuàng)建

這個(gè)在上一篇多線程(一)已經(jīng)詳細(xì)介紹了一部分,這里就不贅述了

線程創(chuàng)建使用的函數(shù)是 pthread_create

函數(shù):pthread_create
 
作用: pthread_create - create a new thread(創(chuàng)建一個(gè)新線程)
 
 
頭文件:#include <pthread.h>
 
函數(shù)原型
 int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);
 
參數(shù)
    第一個(gè)參數(shù)thread,代表線程ID,是一個(gè)輸出型參數(shù),pthread_t是一個(gè)無(wú)符號(hào)整數(shù)
    第二次參數(shù)attr,用于設(shè)置創(chuàng)建線程的屬性,傳入空表示使用默認(rèn)屬性
    第三個(gè)參數(shù)start_routine,是一個(gè)函數(shù)的地址,該參數(shù)表示新線程啟動(dòng)后要跳轉(zhuǎn)執(zhí)行的代碼
    第四個(gè)參數(shù)arg,是start_routine函數(shù)的參數(shù),用于傳入
 
返回值
成功返回0,失敗返回錯(cuò)誤碼

下面進(jìn)行代碼測(cè)試,讓主線程創(chuàng)建一批線程,注意循環(huán)創(chuàng)建線程時(shí)沒有進(jìn)行sleep

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;

void* start_routine(void* args)
{
    string name = static_cast<const char*>(args);//static_cast 安全的進(jìn)行強(qiáng)制類型轉(zhuǎn)換,C++11
    while(1)
    {
        cout << "new thread create success, 線程編號(hào):" << name << endl;
        sleep(1);
    }
}

int main()
{
#define NUM 10
    //創(chuàng)建一批線程
    for(int i = 0; i < NUM; i++)
    {
        pthread_t tid;
        char namebuffer[64];
        snprintf(namebuffer, sizeof(namebuffer), "%s:%d", "thread", i+1);//i+1 使線程下標(biāo)從1開始
        // pthread_create(&tid, nullptr, start_routine, (void*)"thread one");
        pthread_create(&tid, nullptr, start_routine, namebuffer);//給線程帶上編號(hào)
        //沒有進(jìn)行 sleep
        //sellp(1);
    }
    //主線程
    while(1)
    {
        cout << "new thread create success, I am main thread" << endl;
        sleep(1);
    }

    return 0;
}

編譯運(yùn)行,觀察現(xiàn)象,現(xiàn)象一:線程的編號(hào)都是一樣的,并不是我們預(yù)想的從 1、2、3...開始

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

保持進(jìn)程運(yùn)行,ps -aL 查看,10個(gè)線程確實(shí)創(chuàng)建出來了,加上主線程一共 11個(gè)線程

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

把 sleep 注釋的代碼放開,再次編譯運(yùn)行

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

運(yùn)行結(jié)果,現(xiàn)象二:編號(hào) 1-10 都有了

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

現(xiàn)象一解釋:

  • 主線程創(chuàng)建新線程太快了,新線程都沒有機(jī)會(huì)運(yùn)行,主線程就把10個(gè)新線程創(chuàng)建完畢了,
  • 而傳參namebuffer傳過去的是 緩沖區(qū)namebuffer的起始地址,
  • 第十個(gè)線程創(chuàng)建完成之后,緩沖區(qū)的內(nèi)容都被第十個(gè)線程的編號(hào)內(nèi)容覆蓋了,所以第一次現(xiàn)象線程的編號(hào)都是 10

注意:創(chuàng)建的新線程誰(shuí)先運(yùn)行??答案是不確定,完全由調(diào)度器決定

上面的 start_routine函數(shù),被多個(gè)執(zhí)行流執(zhí)行,該函數(shù)處于可重入狀態(tài)。start_routine函數(shù)也是可重入函數(shù),因?yàn)闆]有產(chǎn)生二義性

測(cè)試代碼,對(duì)每個(gè)線程的的cnt進(jìn)行取地址,觀察地址是否相同

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;

//把參數(shù)封成結(jié)構(gòu)體
class ThreadData
{
public:
    pthread_t tid;
    char namebuffer[64];
};

void* start_routine(void* args)
{
   ThreadData* td = static_cast<ThreadData*>(args);//static_cast 安全的進(jìn)行強(qiáng)制類型轉(zhuǎn)換,C++11
   int cnt = 10;
    while(cnt)
    {
        cout << "cnt:" << cnt-- << "  &cnt:" << &cnt << endl;
        sleep(1);
    }
    delete td;
    return nullptr;
}

int main()
{
#define NUM 10
    //創(chuàng)建一批線程
    for(int i = 0; i < NUM; i++)
    {
        ThreadData* td = new ThreadData();//每次循環(huán)new的都是一個(gè)新對(duì)象
        snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i+1);//i+1 使線程下標(biāo)從1開始
        pthread_create(&td->tid, nullptr, start_routine, td);
    }
    //主線程
    while(1)
    {
        cout << "new thread create success, name:main thread" << endl;
        sleep(1);
    }

    return 0;
}

編譯運(yùn)行,觀察到每個(gè)線程的cnt地址都不一樣

在函數(shù)內(nèi)部定義的變量叫局部變量,具有臨時(shí)性,在多線程的情況下依舊適用,因?yàn)槊總€(gè)線程都有自己的獨(dú)立棧結(jié)構(gòu)

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

獲取線程ID?

常見獲取線程ID的方式有兩種:

  • 創(chuàng)建線程時(shí)通過輸出型參數(shù)獲得
  • 通過調(diào)用pthread_self函數(shù)獲得

?man 3 pthread_self 查看:

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

函數(shù):pthread_self

頭文件:#include <pthread.h>

函數(shù)原型: pthread_t pthread_self(void);

?測(cè)試代碼,創(chuàng)建一個(gè)新線程,新線程獲取自己的線程ID

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

void* start_routine(void* args)
{
    string name = static_cast<const char*>(args);
    while(1)
    {
        cout << name << " running..., ID:" << pthread_self() << endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, start_routine, (void*)"thread 1:");
    while(1)
    {
        cout << "main thread" << endl;
        sleep(1);
    }
    return 0;
}

編譯運(yùn)行

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

3.3?線程終止

如果需要只終止某個(gè)線程而不終止整個(gè)進(jìn)程,可以有三種方法:

  1. 從線程函數(shù)return。這種方法對(duì)主線程不適用,從main函數(shù)return相當(dāng)于調(diào)用exit,整個(gè)進(jìn)程退出
  2. 線程可以調(diào)用 pthread_ exit 終止自己
  3. 一個(gè)線程可以調(diào)用 pthread_ cancel 終止同一進(jìn)程中的另一個(gè)線程

return終止線程

在線程中使用return代表當(dāng)前線程退出,但是在main函數(shù)中使用return代表整個(gè)進(jìn)程退出,也就是說只要主線程退出了那么整個(gè)進(jìn)程就退出了,此時(shí)該進(jìn)程曾經(jīng)申請(qǐng)的資源就會(huì)被釋放,而其他線程會(huì)因?yàn)闆]有了資源,自然而然的也退出了

測(cè)試代碼,主線程創(chuàng)建3個(gè)新線程后,休眠2秒,然后進(jìn)行return,那么整個(gè)進(jìn)程也就退出了?

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;

class ThreadData
{
public:
    pthread_t tid;
    char namebuffer[64];
};

void* start_routine(void* args)
{
   ThreadData* td = static_cast<ThreadData*>(args);//static_cast 安全的進(jìn)行強(qiáng)制類型轉(zhuǎn)換,C++11
   int cnt = 10;
    while(cnt)
    {
        cout << "new thread create success, name:" << td->namebuffer << "  cnt:" << cnt-- << endl;
        sleep(1);
    }
    delete td;
    return nullptr;
}

int main()
{
#define NUM 3
    //創(chuàng)建一批線程
    for(int i = 0; i < NUM; i++)
    {
        ThreadData* td = new ThreadData();
        snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i+1);//i+1 使線程下標(biāo)從1開始
        pthread_create(&td->tid, nullptr, start_routine, td);
    }
    //主線程
    cout << "new thread create success, name:main thread" << endl;
    sleep(2);//主線程兩秒后退出
    return 0;
}

?編譯運(yùn)行,2秒后整個(gè)進(jìn)程退出

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

如果其他線程執(zhí)行到return,代表該線程結(jié)束,線程退出

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

pthread_exit函數(shù)終止線程

注意:exit 是用來終止進(jìn)程的,任何一個(gè)執(zhí)行流調(diào)用 exit,都會(huì)使整個(gè)進(jìn)程退出

pthread_exit函數(shù)的功能就是終止線程,man 3 pthread_exit 查看:

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

  • 函數(shù):pthread_exit
  • 頭文件:#include <pthread.h>
  • 函數(shù)原型: void pthread_exit(void *retval);
  • 參數(shù)retval,線程退出時(shí)的退出碼信息,如果不關(guān)心,可以設(shè)置為nullptr
  • 無(wú)返回值,跟進(jìn)程一樣,線程結(jié)束的時(shí)候無(wú)法返回它的調(diào)用者(自身)

例如,在下面代碼中,創(chuàng)建了3個(gè)線程,我們使用pthread_exit函數(shù)終止線程

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;

class ThreadData
{
public:
    pthread_t tid;
    char namebuffer[64];
};

void* start_routine(void* args)
{
   ThreadData* td = static_cast<ThreadData*>(args);//static_cast 安全的進(jìn)行強(qiáng)制類型轉(zhuǎn)換,C++11
   int cnt = 10;
    while(cnt)
    {
        cout << "new thread create success, name:" << td->namebuffer << "  cnt:" << cnt-- << endl;
        sleep(1);
        pthread_exit(nullptr);//每個(gè)線程執(zhí)行到這里就會(huì)退出
    }
    delete td;
    return nullptr;
}

int main()
{
#define NUM 3
    //創(chuàng)建一批線程
    for(int i = 0; i < NUM; i++)
    {
        ThreadData* td = new ThreadData();
        snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i+1);
        pthread_create(&td->tid, nullptr, start_routine, td);
    }
    //主線程
    while(1)
    {
        cout << "new thread create success, name: main thread" << endl;
        sleep(1);
    }
    return 0;
}

編譯運(yùn)行

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

需要注意:pthread_exit或者return返回的指針?biāo)赶虻膬?nèi)存單元必須是全局的或者是用malloc分配的,不能在線程函數(shù)的棧上分配,因?yàn)楫?dāng)其它線程得到這個(gè)返回指針時(shí)線程函數(shù)已經(jīng)退出了?

pthread_cancel函數(shù)取消線程

線程是可以被取消的,我們可以使用pthread_cancel函數(shù)取消某一個(gè)線程

man 3 pthread_cancel 查看:

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

函數(shù):pthread_cancel

頭文件:#include <pthread.h>

函數(shù)原型:
        int pthread_cancel(pthread_t thread);

參數(shù):
    thread:被取消線程的ID

返回值:線程取消成功返回0,失敗返回錯(cuò)誤碼

?測(cè)試代碼,讓線程執(zhí)行5秒后再取消線程

#include <iostream>
#include <vector>
#include <cassert>
#include <unistd.h>
#include <pthread.h>
using namespace std;

class ThreadData
{
public:
    int number; //線程編號(hào)
    pthread_t tid;//線程ID
    char namebuffer[64];//緩沖區(qū)
};

void* start_routine(void* args)
{
    ThreadData* td = static_cast<ThreadData*>(args);
   int cnt = 10;
    while(cnt)
    {
        cout << td->namebuffer << "  cnt:" << cnt-- << endl;
        sleep(1);
    }
    pthread_exit((void*)td->number);
}

int main()
{
    vector<ThreadData*> threads;
#define NUM 5
    //創(chuàng)建一批線程
    for(int i = 0; i < NUM; i++)
    {
        ThreadData* td = new ThreadData();
        td->number = i+1;//線程編號(hào)
        snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", td->number);//把需要傳遞的參數(shù)信息格式化到緩沖區(qū)namebuffer中
        pthread_create(&td->tid, nullptr, start_routine, td);
        threads.push_back(td);//把每個(gè)線程的信息push到threads里面
    }
//主線程
   for(auto& iter : threads)
   {
        cout << "create thread: " << iter->namebuffer << " : " << iter->tid << " success" << endl;
   }
   sleep(5);
   //線程取消
   for(auto& iter : threads)
   {
        pthread_cancel(iter->tid);
        cout << "pthread_cancel: " << iter->namebuffer << endl;
   }
   //線程等待
   for(auto& iter : threads)
   {
        void* ret = nullptr;
        int n = pthread_join(iter->tid, &ret);
        assert(n == 0);
        cout << "join: " << iter->namebuffer <<  " success, thread_exit_code: " << (long long)ret << endl;
        delete iter;
   }
   cout << "main thread quit" << endl;
    return 0;
}

編譯運(yùn)行,

注意:一個(gè)線程被取消,它的退出碼是 -1

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

3.4?線程等待

一個(gè)線程被創(chuàng)建出來,這個(gè)線程就如同進(jìn)程一般,也是需要被等待的。如果主線程不對(duì)新線程進(jìn)行等待,那么這個(gè)新線程的資源也是不會(huì)被回收的。所以線程需要被等待,如果不等待會(huì)產(chǎn)生類似于“僵尸進(jìn)程”的問題,也就是內(nèi)存泄漏,關(guān)于線程產(chǎn)生類似于“僵尸進(jìn)程”的問題,我們無(wú)法查看,線程并沒有類似于僵尸進(jìn)程的概念

  • 已經(jīng)退出的線程,其空間沒有被釋放,仍然在進(jìn)程的地址空間內(nèi)。
  • 創(chuàng)建新的線程不會(huì)復(fù)用剛才退出線程的地址空間

進(jìn)行線程等待的函數(shù)是 pthread_join,功能:進(jìn)行線程等待

man 3 pthread_join 查看:

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

函數(shù):pthread_join

頭文件:?#include <pthread.h>

函數(shù)原型:int pthread_join(pthread_t thread, void **retval);?

參數(shù):
    thread:被等待線程的ID
    retval:線程退出時(shí)的退出碼信息,不關(guān)心設(shè)置為nullptr

返回值:
線程等待成功返回0,失敗返回錯(cuò)誤碼

例如,在下面的測(cè)試代碼中我們先不關(guān)心線程的退出信息,進(jìn)行線程等待

#include <iostream>
#include <vector>
#include <cassert>
#include <unistd.h>
#include <pthread.h>
using namespace std;

class ThreadData
{
public:
    pthread_t tid;
    char namebuffer[64];
};

void* start_routine(void* args)
{
   ThreadData* td = static_cast<ThreadData*>(args);
   int cnt = 5;
    while(cnt)
    {
        cout << td->namebuffer << "  cnt:" << cnt-- << endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    vector<ThreadData*> threads;
#define NUM 5
    //創(chuàng)建一批線程
    for(int i = 0; i < NUM; i++)
    {
        ThreadData* td = new ThreadData();
        snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i+1);//i+1 使線程下標(biāo)從1開始
        pthread_create(&td->tid, nullptr, start_routine, td);
        threads.push_back(td);//把每個(gè)線程的信息push到threads里面
    }
    //主線程
   for(auto& iter : threads)
   {
        cout << "create thread: " << iter->namebuffer << " : " << iter->tid << " success" << endl;
   }
   //線程等待
   for(auto& iter : threads)
   {
        int n = pthread_join(iter->tid, nullptr);
        assert(n == 0);
        cout << "join: " << iter->namebuffer << "success" << endl;
        delete iter;
   }
   cout << "main thread quit" << endl;
    return 0;
}

編譯運(yùn)行

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

下面談線程退出碼的問題,即返回值的問題

  • void* retval 和 void** retval 有什么關(guān)系??
  • 線程函數(shù)start_routine函數(shù)的返回值類型也是 void*,?start_routine函數(shù)的返回值返回到哪里??
  • 我們?cè)趺传@取線程的退出碼,即線程的返回值??

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

  • pthread_join函數(shù)的參數(shù) void** retval 是一個(gè)輸出型參數(shù),用來獲取線程函數(shù)結(jié)束時(shí),返回的退出結(jié)果
  • void** retval 是用來獲取線程函數(shù)返回的退出結(jié)果,因?yàn)榫€程函數(shù)的返回值是 void*,所以需要用 void** 來接受 void*
  • 注意:線程函數(shù)返回的退出結(jié)果是返回在線程庫(kù)當(dāng)中,參數(shù) void** retval 需要去線程庫(kù)里面接受才可以返回

tips:指針是一個(gè)地址(字面值),是一個(gè)右值,指針變量是一個(gè)變量(變量里面保存著指針的地址),是一個(gè)左值,現(xiàn)在使用的Linux一般是64位的,所以指針是占8字節(jié)的

測(cè)試代碼,線程函數(shù)返回 66666,pthread_join函數(shù)的第二個(gè)參數(shù)去線程庫(kù)里面接受再返回

#include <iostream>
#include <vector>
#include <cassert>
#include <unistd.h>
#include <pthread.h>
using namespace std;

class ThreadData
{
public:
    int number; //線程編號(hào)
    pthread_t tid;//線程ID
    char namebuffer[64];//緩沖區(qū)
};

void* start_routine(void* args)
{
    ThreadData* td = static_cast<ThreadData*>(args);
   int cnt = 5;
    while(cnt)
    {
        cout << td->namebuffer << "  cnt:" << cnt-- << endl;
        sleep(1);
    }
    //return (void*)td->number;//返回線程的編號(hào),返回在線程庫(kù)中,函數(shù)的返回類型是void*,需要進(jìn)行強(qiáng)轉(zhuǎn)void*
    return (void*)66666;//方便觀察
}

int main()
{
    vector<ThreadData*> threads;
#define NUM 5
    //創(chuàng)建一批線程
    for(int i = 0; i < NUM; i++)
    {
        ThreadData* td = new ThreadData();
        td->number = i+1;//線程編號(hào)
        snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", td->number);//把需要傳遞的參數(shù)信息格式化到緩沖區(qū)namebuffer中
        pthread_create(&td->tid, nullptr, start_routine, td);
        threads.push_back(td);//把每個(gè)線程的信息push到threads里面
    }
//主線程
   for(auto& iter : threads)
   {
        cout << "create thread: " << iter->namebuffer << " : " << iter->tid << " success" << endl;
   }
   //線程等待
   for(auto& iter : threads)
   {
        void* ret = nullptr;//用于接收線程函數(shù)的返回值
        int n = pthread_join(iter->tid, &ret);//對(duì)ret取地址 == void**,需要去線程庫(kù)中接收,再返回
        assert(n == 0);
        //原來ret是void*,需要強(qiáng)轉(zhuǎn)int,恢復(fù)整型; 
        //由于我所處的平臺(tái)是64位的,指針是8字節(jié),不能用int進(jìn)行強(qiáng)轉(zhuǎn),會(huì)報(bào)錯(cuò),因?yàn)閕nt是4字節(jié),
        //需要用 long long 進(jìn)行強(qiáng)轉(zhuǎn),long long 是8字節(jié)
        cout << "join: " << iter->namebuffer <<  " success, threadnumber: " << (long long)ret << endl;
        delete iter;
   }
   cout << "main thread quit" << endl;
    return 0;
}

編譯運(yùn)行,pthread_join函數(shù)成功獲取線程函數(shù)的返回值

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

解釋如下圖:

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

pthread_exit函數(shù):void pthread_exit(void *retval),這個(gè)也是返回線程的退出信息,測(cè)試結(jié)果如下:

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

注意:調(diào)用pthread_join函數(shù)的線程將掛起等待,直到線程終止等待成功。

int pthread_join(pthread_t thread, void **retval);

thread線程以不同的方法終止,通過pthread_join得到的終止?fàn)顟B(tài)是不同的,總結(jié)如下:

  • 如果thread線程通過return返回,retval 所指向的單元里存放的是thread線程函數(shù)的返回值。
  • 如果thread線程被別的線程調(diào)用 pthread_ cancel 異常終掉,retval 所指向的單元里存放的是常數(shù) PTHREAD_ CANCELED(-1)。
  • 如果thread線程是自己調(diào)用 pthread_exit 終止的,retval 所指向的單元存放的是傳給 pthread_exit 的參數(shù)。
  • 如果對(duì)thread線程的終止?fàn)顟B(tài)不感興趣,可以傳空給 retval 參數(shù)

3.5 線程分離

  • 默認(rèn)情況下,新創(chuàng)建的線程是joinable(可以被等待)的,線程退出后,需要對(duì)其進(jìn)行 pthread_join 操作,否則無(wú)法釋放資源,從而造成系統(tǒng)泄漏
  • 如果不關(guān)心線程的返回值,join是一種負(fù)擔(dān),這個(gè)時(shí)候我們可以將該線程進(jìn)行分離,當(dāng)線程退出時(shí),自動(dòng)釋放線程資源
  • 分離線程的函數(shù)叫做pthread_detach

man 3 pthread_detach 查看:

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

函數(shù):pthread_detach
detach:分開,脫離

頭文件:#include <pthread.h>

函數(shù)原型:
        int pthread_detach(pthread_t thread);

參數(shù):
    thread:被分離線程的ID

返回值:
    線程分離成功返回0,失敗返回錯(cuò)誤碼

?測(cè)試代碼,創(chuàng)建了一個(gè)新線程,然后對(duì)新線程進(jìn)行分離,那么此后主線程就不需要在對(duì)新線程進(jìn)行join了

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

string changeID(const pthread_t& thread_id)
{
    char tid[128];
    snprintf(tid, sizeof(tid), "0x%x", thread_id);
    return tid;
}

void* start_routine(void* args)
{
    string threadname = static_cast<const char*>(args);
    int cnt = 5;
    while(cnt--)
    {
        cout << threadname << " running..., threadID:" << changeID(pthread_self()) << endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, start_routine, (void*)"thread 1");
    pthread_detach(tid);//分離線程

    //線程默認(rèn)是 joinable的,線程分離了之后不允許進(jìn)行等待
    //pthread_join(tid, nullptr);
    string mainID = changeID(pthread_self());//主線程ID
    while(1)
    {
        cout << "main running..., mainID:" << mainID << ", new threadID:" << changeID(tid) << endl;
        sleep(1);
    }
    return 0;
}

編譯運(yùn)行

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

3.6 重新認(rèn)識(shí)pthread庫(kù)

從語(yǔ)言上理解pthread庫(kù)

C++11也有自己的線程庫(kù),測(cè)試代碼如下,使用C++11的線程庫(kù)創(chuàng)建一個(gè)新線程

#include <iostream>
#include <unistd.h>
#include <thread>

void thread_run()
{
    while (true)
    {
        std::cout << "我是新線程..." << std::endl;
        sleep(1);
    }
}

int main()
{
    //創(chuàng)建新線程
    std::thread t1(thread_run);
    //主線程
    while (true)
    {
        std::cout << "我是主線程..." << std::endl;
        sleep(1);
    }
    //線程等待
    t1.join();

    return 0;
}

?編譯運(yùn)行,在Linux上也能創(chuàng)建線程?

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

如何看待C++11中的線程庫(kù)??

任何語(yǔ)言,在linux中如果要實(shí)現(xiàn)多線程,必定要是用pthread庫(kù)。C++11的多線程,在Linux環(huán)境中,本質(zhì)是對(duì)pthread庫(kù)的封裝?

從內(nèi)核上理解pthread庫(kù)

  • 用戶創(chuàng)建的線程在 pthread庫(kù)中,pthread庫(kù)又會(huì)幫我們調(diào)用系統(tǒng)調(diào)用接口clone,幫我們創(chuàng)建輕量級(jí)線程
  • Linux用戶級(jí)線程 : 內(nèi)核輕量級(jí)線程 = 1 : 1
  • 這兩個(gè)的關(guān)系是一對(duì)一的,用戶創(chuàng)建一個(gè)線程,在內(nèi)核中就會(huì)創(chuàng)建一個(gè)輕量級(jí)進(jìn)程
  • 用戶關(guān)心的線程屬性在 pthread中,而內(nèi)核提供的是線程執(zhí)行流的調(diào)度

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

如何理解我們前面創(chuàng)建線程時(shí)的線程ID??如何理解每個(gè)線程的獨(dú)立棧結(jié)構(gòu)???

  • pthread_t類型的線程ID,本質(zhì)就是一個(gè)進(jìn)程地址空間上的一個(gè)地址

進(jìn)程運(yùn)行時(shí)動(dòng)態(tài)庫(kù)被加載到內(nèi)存,然后通過頁(yè)表映射到進(jìn)程地址空間中的共享區(qū),此時(shí)該進(jìn)程內(nèi)的所有線程都是能看到這個(gè)動(dòng)態(tài)庫(kù)的

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

每一個(gè)新線程在共享區(qū)都有這樣一塊區(qū)域?qū)π戮€程的描述,因此我們要找到一個(gè)用戶級(jí)線程只需要找到該線程內(nèi)存塊的起始地址,然后就可以獲取到該線程的各種信息

  • 所以,pthread_t類型的線程ID,本質(zhì)就是一個(gè)進(jìn)程地址空間上的一個(gè)地址,指向這個(gè)線程結(jié)構(gòu)體的開始地址
  • 每個(gè)線程都有一個(gè)獨(dú)立的棧結(jié)構(gòu),這個(gè)棧就在描述線程的結(jié)構(gòu)體里面,即在pthread庫(kù)中
  • 每個(gè)線程創(chuàng)建之后,都是使用自己獨(dú)立的棧
  • 主線程所用的棧,也就是我平時(shí)所說的棧,地址空間中的棧

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

3.7 封裝線程

目的:對(duì)Linux線程接口進(jìn)行封裝,使封裝后的接口可以像C++11線程庫(kù)里面的一樣使用

Thread.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cassert>
#include <functional>
#include  <pthread.h>

// 異常 == if: 意料之外用異?;蛘遡f判斷
// assert: 意料之中用assert

//Context需要使用Thread,需要聲明一下
class Thread;

//上下文,當(dāng)成一個(gè)大號(hào)的結(jié)構(gòu)體
class Context
{
public:
    Thread *this_;
    void *args_;
public:
    Context():this_(nullptr), args_(nullptr)
    {}
    ~Context(){}
};

class Thread
{
public:
    typedef std::function<void*(void*)> func_t;//線程函數(shù)
    const int num = 1024;
public:
    //構(gòu)造初始化并創(chuàng)建線程
    Thread(func_t func, void *args = nullptr, int number = 0): func_(func), args_(args)
    {
        char buffer[num];
        snprintf(buffer, sizeof buffer, "thread-%d", number);
        name_ = buffer;
        
        Context *ctx = new Context();
        ctx->this_ = this;
        ctx->args_ = args_;
        int n = pthread_create(&tid_, nullptr, start_routine, ctx);
        //編譯debug的方式發(fā)布的時(shí)候存在,release方式發(fā)布,assert就不存在了,n就是一個(gè)定義了
        assert(n == 0); 
        //但是沒有被使用的變量,在有些編譯器下會(huì)有warning
        (void)n;
    }

    void *run(void *args){ return func_(args); }

    // 類內(nèi)成員,有缺省參數(shù)this指針,無(wú)法直接調(diào)用對(duì)應(yīng)的函數(shù),func_(args_)error
    // 在類內(nèi)創(chuàng)建線程,想讓線程執(zhí)行對(duì)應(yīng)的方法,需要將方法設(shè)置成為static
    // 靜態(tài)方法不能調(diào)用成員方法或者成員變量
    // 所以需要借助一個(gè)結(jié)構(gòu)體Context,完成調(diào)用函數(shù)的工作
    static void *start_routine(void *args) 
    {
        Context *ctx = static_cast<Context *>(args);
        void *ret = ctx->this_->run(ctx->args_);
        delete ctx;
        return ret;
    }

    // 線程等待函數(shù)
    void join()
    {
        int n = pthread_join(tid_, nullptr);
        assert(n == 0);
        (void)n;
    }
    ~Thread() {}
private:
    std::string name_;//線程名稱
    func_t func_;//線程要執(zhí)行的函數(shù)
    void* args_;//給線程函數(shù)傳遞的參數(shù)
    pthread_t tid_;//線程ID
};

測(cè)試代碼

#include <iostream>
#include <unistd.h>
#include "Thread.hpp"
using namespace std;

//新線程
void* start_routine(void* args)
{
    string name = static_cast<const char*>(args);
    while(1)
    {
        cout << "new thread create success, name: " << name << endl;
        sleep(1);
    }
}

int main()
{
    //不想傳(void*)"thread 1", 1 參數(shù),還需要進(jìn)行封裝
    Thread t1(start_routine, (void*)"thread 1", 1);
    while(1)
    {
        cout << "main thread" << endl;
        sleep(1);
    }
    return 0;
}

運(yùn)行結(jié)果

『Linux』第九講:Linux多線程詳解(二)_ 線程控制

線程創(chuàng)建完結(jié),下一篇進(jìn)入互斥量和生產(chǎn)消費(fèi)者模型

--------------------- END ----------------------文章來源地址http://www.zghlxwxcb.cn/news/detail-430233.html

「 作者 」 楓葉先生
「 更新 」 2023.4.30
「 聲明 」 余之才疏學(xué)淺,故所撰文疏漏難免,
          或有謬誤或不準(zhǔn)確之處,敬請(qǐng)讀者批評(píng)指正。

到了這里,關(guān)于『Linux』第九講:Linux多線程詳解(二)_ 線程控制的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 第九講 單片機(jī)驅(qū)動(dòng)彩色液晶屏 控制RA8889軟件:顯存操作

    第九講 單片機(jī)驅(qū)動(dòng)彩色液晶屏 控制RA8889軟件:顯存操作

    目錄 第一講 單片機(jī)最小系統(tǒng)STM32F103C6T6通過RA8889驅(qū)動(dòng)彩色液晶屏播放視頻 第二講 單片機(jī)最小系統(tǒng)STM32F103C6T6控制RA8889驅(qū)動(dòng)彩色液晶屏硬件框架 第三講 單片機(jī)驅(qū)動(dòng)彩色液晶屏 控制RA8889軟件:如何初始化 第四講 單片機(jī)驅(qū)動(dòng)彩色液晶屏 控制RA8889軟件:繪圖 第五講 單片機(jī)驅(qū)動(dòng)彩色液

    2024年01月16日
    瀏覽(19)
  • MySQL第九講·索引怎么提高查詢的速度?

    MySQL第九講·索引怎么提高查詢的速度?

    你好,我是安然無(wú)虞。 在超市信息系統(tǒng)剛剛開始運(yùn)營(yíng)的時(shí)候,因?yàn)閿?shù)據(jù)量很少,每一次的查詢都能很快拿到結(jié)果。但是,系統(tǒng)運(yùn)轉(zhuǎn)時(shí)間長(zhǎng)了以后,數(shù)據(jù)量不斷地累積,變得越來越龐大,很多查詢的速度就變得特別慢。這個(gè)時(shí)候,我們就采用了 MySQL 提供的高效訪問數(shù)據(jù)的方法

    2024年02月04日
    瀏覽(19)
  • Git第九講 Git stash暫存消息

    git stash 是一個(gè)在 Git 中用于臨時(shí)保存未提交的更改的命令。它可以幫助你在切換分支或處理緊急任務(wù)時(shí),將當(dāng)前工作目錄中的修改保存起來,以便稍后恢復(fù)。 git stash 的使用方法如下: 當(dāng)你想要暫存當(dāng)前的修改時(shí),運(yùn)行以下命令: 可選地,你可以提供一個(gè)簡(jiǎn)短的描述性消息,

    2024年02月08日
    瀏覽(24)
  • Redis 7 第九講 微服務(wù)集成Redis 應(yīng)用篇

    Redis 7 第九講 微服務(wù)集成Redis 應(yīng)用篇

    ????????Jedis是redis的java版本的客戶端實(shí)現(xiàn),使用Jedis提供的Java API對(duì)Redis進(jìn)行操作,是Redis官方推崇的方式;并且,使用Jedis提供的對(duì)Redis的支持也最為靈活、全面;不足之處,就是編碼復(fù)雜度較高。? ?直連案列 ? ?獲取Redis連接對(duì)象 Redis 案列 關(guān)閉Jedis對(duì)象? ?池案列 ?獲取

    2024年02月09日
    瀏覽(17)
  • 設(shè)計(jì)模式第九講:常見重構(gòu)技巧 - 去除不必要的!=

    設(shè)計(jì)模式第九講:常見重構(gòu)技巧 - 去除不必要的!=

    項(xiàng)目中會(huì)存在大量判空代碼,多么丑陋繁冗!如何避免這種情況?我們是否濫用了判空呢?本文是設(shè)計(jì)模式第九講,講解常見重構(gòu)技巧:去除不必要的!= 通常是這樣的 初步的,使用Apache Commons,Guvava,Hutool等 StringUtils 考慮用Assert斷言 逐級(jí)判斷空,還是拋出自定義異常,還是

    2024年02月11日
    瀏覽(24)
  • 前端技術(shù)學(xué)習(xí)第九講:VUE基礎(chǔ)語(yǔ)法---VUE常用指令

    前端技術(shù)學(xué)習(xí)第九講:VUE基礎(chǔ)語(yǔ)法---VUE常用指令

    在VUE學(xué)習(xí)中,通常使用相關(guān)指令使VUE對(duì)象中的內(nèi)容與網(wǎng)頁(yè)進(jìn)行掛載綁定,是我們的數(shù)據(jù)與視圖之間產(chǎn)生關(guān)聯(lián),完成漸進(jìn)式動(dòng)態(tài)效果。VUE指令都會(huì)以“v-”開頭。 指令名 描述 v-text 將文本內(nèi)容掛載到頁(yè)面元素中 v-html 將html代碼展示到頁(yè)面元素中 v-bind 將內(nèi)容解析成為js,綁定至頁(yè)

    2024年02月13日
    瀏覽(22)
  • 【Linux】詳解線程控制 -- 線程用法 | 線程等待 | 線程ID及地址空間布局

    【Linux】詳解線程控制 -- 線程用法 | 線程等待 | 線程ID及地址空間布局

    (關(guān)于 用戶 → 庫(kù) → OS :具體可看下面線程地址空間布局) 這個(gè) clone 我們不用,這是OS提供給第三方庫(kù)所用的接口 與線程有關(guān)的函數(shù)構(gòu)成了一個(gè)完整的系列,絕大多數(shù)函數(shù)的名字都是以“ pthread_ ”開頭的,要使用這些函數(shù)庫(kù),要通過引入頭文 pthread.h ,鏈接這些線程函數(shù)庫(kù)時(shí)要

    2024年02月15日
    瀏覽(20)
  • 考研數(shù)二第九講 函數(shù)凹凸性證明,求極值以及拐點(diǎn)及漸近線

    考研數(shù)二第九講 函數(shù)凹凸性證明,求極值以及拐點(diǎn)及漸近線

    函數(shù)的凹凸性即對(duì)一個(gè)在某區(qū)間A上連續(xù)的函數(shù),它的圖像上凸或者上凹,則分別稱為凸函數(shù)或者凹函數(shù)。而對(duì)于在某個(gè)區(qū)間內(nèi)既有凹圖像又有凸圖像,則將凹圖像所在區(qū)間稱為函數(shù)的凹區(qū)間,凸圖像所在區(qū)間則稱為凸區(qū)間。 中點(diǎn)定義法 同樣是觀察凹凸函數(shù)的圖像,發(fā)現(xiàn)凹函

    2024年02月09日
    瀏覽(24)
  • 【Java基礎(chǔ)教程】(十五)面向?qū)ο笃?· 第九講:抽象類和接口——定義、限制與應(yīng)用的細(xì)節(jié),初窺模板設(shè)計(jì)模式、工廠設(shè)計(jì)模式與代理設(shè)計(jì)模式~

    【Java基礎(chǔ)教程】(十五)面向?qū)ο笃?· 第九講:抽象類和接口——定義、限制與應(yīng)用的細(xì)節(jié),初窺模板設(shè)計(jì)模式、工廠設(shè)計(jì)模式與代理設(shè)計(jì)模式~

    掌握 抽象類和接口的定義、使用、區(qū)別、常見設(shè)計(jì)模式; 抽象類是代碼開發(fā)中的重要組成部分,利用抽象類可以明確地定義子類需要覆寫的方法,這樣相當(dāng)于在語(yǔ)法程度上對(duì)子類進(jìn)行了嚴(yán)格的定義限制,代碼的開發(fā)也就更加標(biāo)準(zhǔn)。下面具體介紹抽象類的概念。 普通類可以直

    2024年02月16日
    瀏覽(25)
  • 【Linux】Linux線程概念和線程控制

    【Linux】Linux線程概念和線程控制

    線程是進(jìn)程內(nèi)的一個(gè)執(zhí)行流。 我們知道,一個(gè)進(jìn)程會(huì)有對(duì)應(yīng)的PCB,虛擬地址空間,頁(yè)表以及映射的物理內(nèi)存。所以我們把這一個(gè)整體看做一個(gè)進(jìn)程,即進(jìn)程=內(nèi)核數(shù)據(jù)結(jié)構(gòu)+進(jìn)程對(duì)應(yīng)的代碼和數(shù)據(jù)。我們可以這樣看待虛存:虛擬內(nèi)存決定了進(jìn)程能夠看到的\\\"資源\\\"。因?yàn)槊恳粋€(gè)進(jìn)

    2024年02月04日
    瀏覽(30)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包