線程
線程的概述
在之前,我們常把進程定義為 程序執行的實例,實際不然,進程實際上只是維護應用程序的各種資源,并不執行什么。真正執行具體任務的是線程。
那為什么之前直接執行a.out的時候,沒有這種感受呢?
那是因為每一個進程中都會有一個主線程,我們默認執行的就是這個主線程。
線程創建比進程簡單
進程通過返回值確定 是哪塊進程的代碼。
線程不需要,創建一個線程,比較簡單,像回調函數一樣,調用線程創建函數,在對應函數體中 操作這一線程即可。
從這往下的概述部分 重點(理解背誦)
進程是系統分配資源的基本單位,線程是CPU執行基本調度的基本單位
比如 如果線程是具體某個人,那么進程就是指部門
線程可以看作一個輕量級的進程(LWP:light weight process),在Linux環境下線程的本質仍是進程。
進程 必須至少包含一個線程
線程依賴于進程,線程共享進程的資源,線程的系統資源有(計數器,一組寄存器和棧)
進程結束?當前進程的所有線程 都將立即結束
Linux內核是不區分進程和線程的,只有在用戶層面上進行區分。所以,進程所有操作函數pthread*是庫函數,而并非系統調用
線程共享資源
- 文件描述符
- 每種信號的處理方式
- 當前工作目錄
- 用戶ID和組ID 內存地址空間
線程非共享資源
- 線程id
- 處理器現場和棧指針
- 獨立的棧空間
- errno變量
- 信號屏蔽字
- 調度優先級
線程被CPU調度,因此線程中有調度優先級,且線程間不共享
查看指定進程的線程號的命令:ps -Lf pid(進程號)
線程的API
API介紹用的代碼 較簡短的代碼我用圖片展示。
只要看到了pthread.h 頭文件,我們在編譯的時候就需要加上 -lpthread
pthread_t 是無符號長整型
1、查看線程號
#include <pthread.h>
pthread_t pthread_self(void);
功能:
????????查看線程號
參數:
????????無參
返回值:
????????調用該函數的線程 的 線程ID
代碼演示
代碼運行結果
線程ID(通過pthread_self得到) 和 IPW(輕量級進程)的區別
大家看這張圖,可以看到這兩個值有明顯的區別
在Linux中,線程就是LWP(輕量級進程),全局唯一,由操作系統內核分配,用于系統調度和資源管理。
而
線程ID呢僅在同一進程內有效,是抽象標識符。由?pthread 庫在進程內維護。
2、創建線程
#include <pthread.h>
int pthread_create(pthread_t *thread,
????????????????????????????????const pthread_attr_t *attr,
????????????????????????????????void *(*start_routine) (void *),
????????????????????????????????void *arg);
功能:
????????創建一個線程
參數:
????????thread:線程標識符地址
????????attr:線程屬性結構體地址,通常設置為NULL
????????????????屬性這個參數,我們現在填寫NULL,下面我會詳細說一下這個參數。
????????start_routine:線程函數的入口地址
????????arg:傳遞給線程函數的參數
返回值:
????????成功:0
????????失敗:非0
代碼演示 案例1
注意這里主進程一定要阻塞,因為進程結束,線程也會關閉
代碼運行結果
案例二 創建進程,每個線程有自己的線程函數
代碼運行結果是一樣的,大家只要知道能夠這樣用就可以了。
3、回收線程函數
函數介紹
功能:
????????等待線程結束(此函數會阻塞),并回收線程資源。如果線程已結束,那么該函數會立即返回。
參數:
????????thread:被等待的進程的進程ID
????????retval:用來存儲線程退出狀態的指針的地址
????????這里細說一下:retval的返回值類型我們可以看到是void **,這個變量需要用戶創建,用來存儲創建函數 線程執行函數的 返回值,返回值時void*類型。由于我們要得到它,就要提前創建一個void *的變量,再通過函數修改我們創建的變量為返回值的內容,由于是函數內部要函數外部的變量的值,因此需要傳遞所創建void *的變量的地址,因此時void **類型。
返回值:
????????成功:0
????????失敗:非0
代碼演示
代碼運行結果
注意
由于帶阻塞,因此有順序,如下面這種情況
先等待tid1結束,回收tid1后,才會回收tid2
不管誰先結束,都是先1 后2
進程分離
創建好線程后,當多個任務同時進行,用上面的方法,會阻塞線程的釋放,導致資源浪費(長時間不適用卻霸占內存),因此這里 我們就將其分離出去,把釋放工作交給系統,系統發現它結束,就會釋放
由于它的歸屬權已經歸于系統,此時我們就不可以再對它使用join
注意這里的分離,并不是該線程不依賴于進程,而是將 釋放線程獨立資源 的權限交給了系統,進程還是依賴與進程的,依舊共享進程的空間
函數介紹
#include <pthread.h>
int pthread_detach(pthread_t thread);
功能:
????????使調用線程的獨立資源回收工作與當前進程分離
參數:
????????thread:線程ID
返回值:
????????成功:0
????????失敗:非零
代碼演示 主線程和子線程
本代碼將實現 主線程和子線程 一起運行,并且利用主線程的正常工作,來驗證pthread_detach的不阻塞的特性
代碼運行結果
4、線程的取消和退出
????????注意要退出線程 一定不要調用exit或者_exit 這兩個是退出進程的函數,如果調用這個在線程中知道你的進程是什么,它會將進程退出,進程退出會導致所有的線程退出,那么我們該怎么讓單個線程退出呢?
1、線程的退出(自殺)
#include <pthread.h>
void pthread_exit(void *retval);
函數功能:
????????退出調用線程。一個進程中的多個線程是共享該進程的數據段的,因此通常線程退出后,所占用的資源并不會釋放。
參數:
????????retval:存儲線程退出狀態的指針(return后的數據)
返回值:
????????無
2、線程的取消(他殺)
取消本線程,也可以取消當前進程的其他線程
#include <pthread.h>
int pthread_cancel(pthread_t thread);
功能:
????????退出調用線程。一個進程中的多個線程是共享該進程的數據段的,因此通常線程退出后,所占用的資源并不會釋放。
參數:
????????thread:目標線程ID
返回值:
????????成功:0
????????失敗:出錯編號
注意
????????殺死線程也不是立刻就能完成,必須要到達取消點。
????????取消點:是線程檢查是否被取消,并按請求進行動作的一個位置。通常是一些系統調用。
代碼演示:
代碼功能:子線程1實現5s后自殺,子線程2在7s時殺死子線程3,子線程2在10s時殺死自己。
這里我們會與遇到一個問題:當我們在線程2 中,我們首先需要傳入本線程的名字(線程2),還需要傳入子線程3的線程ID,我們該如何實現傳兩個參數呢?
答案在代碼中,大家自己查看。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>//傳遞兩個參數的辦法就是借助結構體,將線程名和ID作為結構體成員后,將結構體傳入 線程調用函數 中即可
//并且如果要實現tid3的修改同步到結構體內,需要傳遞tid3的指針類型
typedef struct dataDouble
{char name[32];pthread_t *id;
}DATA;//線程調用函數聲明
void *my_fun1(void *arg);
void *my_fun2(void *arg);
void *my_fun3(void *arg);
int main(int argc, char const *argv[])
{//創建線程ID遍歷(存放線程ID)pthread_t tid1,tid2,tid3;DATA *tid2_data = (DATA *)calloc(1,sizeof(DATA));tid2_data->id = &tid3;strcpy(tid2_data->name,"子進程2");//創建線程pthread_create(&tid1,NULL,my_fun1,(void *)"子線程1");pthread_create(&tid2,NULL,my_fun2,(void *)tid2_data);pthread_create(&tid3,NULL,my_fun3,(void *)"子線程3");//釋放線程pthread_detach(tid1);pthread_detach(tid2);pthread_detach(tid3);//阻塞進程while(1);//釋放結構體申請空間,一定要在全部線程結束之后free(tid2_data);return 0;
}
//線程調用函數體實現
void *my_fun1(void *arg)//線程1 在5s的時候自殺
{int i = 0;while(1){sleep(1);printf("----%s的運行時間為:%d\n",(char *)arg,++i);if(i == 5){pthread_exit(NULL);}}
}
void *my_fun2(void *arg)//線程2 在7s的時候殺死線程3,在10s的時候自殺(使用cancel)
{DATA data = *(DATA *)arg;int i = 0;while(1){sleep(1);printf("--------%s的運行時間為:%d\n",data.name,++i);if(i == 7){pthread_cancel(*data.id);}if(i == 10){pthread_cancel(pthread_self());}}
}
void *my_fun3(void *arg)
{int i = 0;while(1){sleep(1);printf("------------%s的運行時間為:%d\n",(char *)arg,++i);}
}
代碼運行結果
結束
代碼重在練習!
代碼重在練習!
代碼重在練習!
今天的分享就到此結束了,希望對你有所幫助,如果你喜歡我的分享,請點贊收藏夾關注,謝謝大家!!!
下篇介紹:線程的屬性介紹,線程池的簡述,多線程的建立