線程控制

POSIX線程庫(用戶級)

與線程有關的函數構成了一個完整的系列,絕大多數函數的名字以“pthread_”開頭
要使用這些函數庫,要通過引入頭文
鏈接這些線程函數庫時要使用編譯器命令的“-lpthread”選項
用戶線程和內核線程是1對1的

一、線程創建

  • pthread_create函數
頭文件:#include<pthread.h>
函數原型:int pthread_create(pthread_t * thread,const pthread_attr_t *attr,void *(*start_routine)(void*),void *arg);
函數功能:創建一個新的線程
返回值:成功返回0,失敗返回錯誤碼
參數:
thread:返回線程ID
attr:設置線程的屬性,attr爲NULL表示使用默認屬性
start_routine:是一個函數地址。線程啓動後要執行的函數
arg:傳給線程啓動函數的參數

pthread_t 無符號長整型

下面是創建線程代碼的具體實現:

#include <stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
void *thread_run(void *arg)
{
    while(1)
    {
        printf("new pthread,thread is : %u,pid :%d\n",pthread_self(),getpid());
        sleep(1);
    }
}
int main()
{
    pthread_t tid;
    pthread_create(&tid,NULL,thread_run,NULL);
    while(1)
    {
        printf("main pthread,thread is : %u,pid %d\n",pthread_self(),getpid());
        sleep(3);
    }
    return 0;
}

按照正常寫Makefile的方法寫會出現下面的問題
這裏寫圖片描述
所以要在Makefile中加上“-lpthread
這裏寫圖片描述
就會運行出正確的結果
這裏寫圖片描述
沒有線程之前,一個進程對應內核裏的一個進程描述符,對應一個進程ID。但是引入線程概念之後,情況發生了變化,一個用戶進程下管轄N個用戶態線程,每個線程作爲一個獨立的調度實體在內核態都有自己的進程描述符,進程和內核的描述符(task_struct)一下子就變成了1:N的關係(一個進程內可以有多個線程)
這裏寫圖片描述

二、線程等待

  • 線程以阻塞方式進行等待

  • 爲什麼需要線程等待?
    1.已經退出的線程,其空間沒有被釋放,仍然在進程的地址空間內
    2.創建新的線程不會複用剛纔退出線程的地址空間

  • pthread_join函數

頭文件:#include<pthread.h>
函數原型:int pthread_join(pthread_t thread,void** value_ptr);
函數功能:讓主線程阻塞,直到所有線程都已經退出。
返回值:調用成功返回0,其他值都爲失敗
參數:
thread:等待退出線程的線程號
value_ptr:退出線程的返回值
  • 此函數的作用:
    1.保證退出時的退出順序
    2.回收新線程退出時的資源情況
    3.獲得新線程退出時結果是否正確的退出返回值

線程等待的具體代碼實現:

#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
void *thread1(void *arg)
{
    printf("thread1 returning...\n");
    int *p = (int*)malloc(sizeof(int));
    *p = 1;
    return (void *)p;
}
void *thread2(void *arg)
{
    printf("thread2 exiting...\n");
    int *p = (int*)malloc(sizeof(int));
    *p = 2;
    pthread_exit((void*)p);
}
void *thread3(void *arg)
{
    while(1)
    {
        printf("thread3 is running...\n");
        sleep(1);
    }
    return NULL;
}
int main()
{
    pthread_t tid;
    void *ret;
    //thread1
    pthread_create(&tid,NULL,thread1,NULL);
    pthread_join(tid,&ret);
    printf("thread return,thread tid : %X, return code : %d\n",tid,*(int*)ret);
    free(ret);
    //thread2
    pthread_create(&tid,NULL,thread2,NULL);
    pthread_join(tid,&ret);
    printf("thread return,thread tid : %X, return code : %d\n",tid,*(int*)ret);
    free(ret);
    //thread3
    pthread_create(&tid,NULL,thread3,NULL);
    sleep(3);
    pthread_cancel(tid);
    pthread_join(tid,&ret);
    if(ret == PTHREAD_CANCELED)
    {
        printf("thread return,thread tid : %X, return code : PTHREAD_CANCELED\n",tid);
    }
    else
    {
        printf("thread return,thread tid : %X, return code : NULL\n",tid);
    }
    free(ret);
    return 0;
}

運行結果如圖所示:
這裏寫圖片描述

三、線程終止

A.進程負責線程正確執行
B.父進程負責進程正確執行
C.執行流負責函數正確執行,想讓父進程獲得子進程退出碼必須以進程退出方式

  • 如果需要只終止某個線程而不是終止整個進程,可以有三種方法:
    1.從線程函數return;這種方法對主線程不適用。從main函數return相當於調用exit
    2.線程可以調用pthread_exit終止自己
    3.一個線程可以調用pthread_cancel終止同一進程中的另一個線程

  • 進程退出的兩種方式:
    1.調用exit();
    2.主進程return

  • pthread_exit函數

頭文件:#include<pthread.h>
函數原型:void pthread_exit(void *value_ptr);
函數功能:線程終止
返回值:無返回值,和進程一樣,線程結束的時候無法返回到它的調用者(自身)
參數:
value_ptr:不要指向一個局部變量

pthread_exit或者return返回的指針所指向的內存單元必須是全局的或者是用malloc分配的,不能在線程函數的棧上分配,因爲當其他線程得到這個返回指針時線程函數已經退出

線程終止的具體代碼實現:

#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
void *thread_run(void *arg)
{
    sleep(5);
    printf("new pthread,thread is : %u , pid : %d\n",pthread_self(),getpid());
    pthread_exit((void*)123);
}
int main()
{
    pthread_t tid;
    pthread_create(&tid,NULL,thread_run,NULL);

    void *ret;
    pthread_join(tid,&ret);
    printf("join new thread success,ret :%d\n",*(int *)ret);
    return 0;
}

5秒後線程終止,主線程join成功
運行結果如圖所示
這裏寫圖片描述

  • pthread_cancel函數
頭文件:#include<pthread.h>
函數原型:int pthread_cancel(pthread_t thread);
函數功能:取消一個執行中的線程
返回值:成功返回0,失敗返回錯誤碼
參數:
thread:線程ID

四、線程的分離與結合屬性:

  • 在任何一個時間點上,線程是可結合的(joinable),或者是分離的(detached)。joinable和分離是衝突的,一個線程不能既是joinable又是分離的。

  • 可結合屬性:一個可結合的線程能夠被其他線程收回其資源和殺死;在被其他線程回收之前,它的存儲器資源(如棧)是不釋放的。

  • 不可結合屬性:一個分離的線程是不能被其他線程回收或殺死的,它的存儲器資源在它終止時由系統自動釋放。

  • 線程不管怎麼分離都會在進程內部執行

  • 默認情況下,線程被創建成可結合的。
    1.爲了避免存儲器泄漏,每個可結合線程都應該被顯示的回收(調用pthread_join)
    當在可結合狀態下,只有當pthread_join()函數返回時,創建的線程纔算終止,才能釋放自己佔用的系統資源,如果一個可結合線程結束運行但沒有被join,則它的狀態類似於殭屍進程還有一部分資源沒有被回收

2.或者通過設置可分離屬性
(1)線程調用函數:pthread_detach(pthread_self());
(2)主線程調用函數:pthread_detach(thread_id);
可分離線程沒有被其他的線程所等待,自己運行結束,線程就終止了,馬上釋放系統資源。一個進程即便被分離,只要發生異常,進程也會被影響

  • 爲什麼要引入線程的可分離屬性呢??
    主線程調用pthread_join後,如果該線程沒有運行結束,主線程會被阻塞,當主線程還要創建新線程來做一些事情時,此時的主線程因爲調用pthread_join而被阻塞,就沒有辦法處理其他事務。所以引入線程的可分離屬性,它不需要主線程來回收它,在其退出系統時自動被系統回收。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章