1. 什麼是線程
線程是程序執行内部的一個執行分支,在一個程序内部運作的多種執行流;内部本質上是多個線程在同一個位址空間運作;第一個pcb稱之為主線程;有多個線程就有多個執行流;一個程序至少有一個線程
2. 圖解線程
PCB2所代表的程序通過vfork建立一個子程序,子程序再vfork一個新的子程序,以此類推産生兩個新的子程序;
此時PCB1、PCB2、PCB3都指向同一塊虛拟位址空間,通過操作把PCB1所指向的虛拟空間的資源(主要是資料和代碼),分成3部分分别給PCB1、PCB2、PCB3
這些程序就被稱為線程,這些線程之間滿足互不幹擾的條件,且這些線程共用同一虛拟空間,并且共用部分資源,在通路這些公共資源時,這些線程可以互相通路到對方的資源
最後,如果大家如果在自學遇到困難,想找一個C++的學習環境,可以加入我們的C++學習圈,點選我加入吧,會節約很多時間,減少很多在學習中遇到的難題
。3. linux下的線程
linux下并沒有真正意義上的線程存在,linux中使用程序來模拟實作線程,父程序建立子程序,子程序執行父程序的一部分代碼,并且與父程序共享同一個位址空間。這些一個一個被建立出來的子程序可看到為線程,這種線程也稱之為輕量級程序
注:輕量級程序(LWP)是一種實作多任務的方法。與普通程序相比,LWP與其他程序共享所有(或大部分)它的邏輯位址空間和系統資源;linux下,CPU看到的所有程序都可以看作為輕量級程序
4. 線程的資源(私有和共享)
共享私有
資料段,代碼段 每個線程都有自己的棧結構
檔案描述符表 上下文,(包含各種計數器的值、程式計數器和棧指針)
每種信号的處理方式 線程id
目前工作的目錄 errno變量(當線程異常退出時的錯誤退出碼)
使用者id和線程組id 信号屏蔽字
排程優先級
5. 線程的優缺點
優點
線程占用的資源比程序少,隻虛複制PCB即可
建立時代價較小
線程間的切換(排程)需要作業系統做的工作少很多
線程間共享資料更容易
在等待慢速 I/O操作結束的同時,程式可執行其他的計算任務。
計算密集型應用,為了能在多處理器系統上運作,将計算分解到多個線程中實作。
I/O密集型應用,為了提高性能,将I/O操作重疊。線程可以同時等待不同的I/O操作。
注:關于I/O密集型和計算密集型可參考這篇文章:CPU-bound(計算密集型) 和I/O bound(I/O密集型
缺點
性能損失(一個很少被外部事件阻塞的計算密集型線程往往無法與其他線程共享一個處理器。如果計算密集型線程的數量比可用的處理器多,那麼就有可能造成較大的性能損失,這裡的性能損失指的是作業系統增加了額外的同步和排程開銷,而可用的資源不變);
健壯性降低(由于線程是共享同一塊虛拟位址空間的,在運作期間,因時間配置設定上的細微偏差或者因共享了不該共享的變量而造成不良影響的可能性是很大的,也就是說線程之間是缺乏保護的);
缺乏通路控制(當線上程中調用某些函數(OS函數,處理signal函數,調用kill/exit函數),可能會導緻線程退出,進而使所有的線程都異常退出);
程式設計難度提高(線程共用同一塊虛拟位址空間,勢必在處理多線程時會有通路同一個資源等問題,此時就涉及到共享資源的處理)
6. 線程控制
6.0 寫在前面
在作業系統内部,它不管什麼程序線程的,它隻以PCB為準,隻有在使用者态裡才有線程的概念。一般實作線程會用到一個POSIX線程庫,在這裡可以通過調用POSIX庫裡的函數來實作有關線程的各種操作。不過核心中也有一種核心級線程。
兩個基本類型:
使用者級線程:管理過程全部由使用者程式完成,作業系統核心心隻對程序進行管理。如POSIX線程庫。
系統級線程(核心級線程):由作業系統核心進行管理。作業系統核心給應用程式提供相應的系統調用和應用程式接口API,以使使用者程式可以建立、執行、撤消線程。
POSIX線程庫
由系統庫支援。線程的建立和撤銷以及線程狀态的變化都由庫函數控制并在目态(user态)完成,與線程相關的控制結構TCB儲存在目态并由系統維護。由于線程對操作不可見(作業系統可見的必然儲存在kernel态由系統維護),系統排程仍以程序為機關(同一程序内線程互相競争),核心棧的個數與程序個數相對性。
與線程有關的函數構成了一個完整的系列,絕大多數函數的名字都是以“pthread_”打頭的
要使用這些庫函數,就要引入頭檔案
gcc在連結這些線程函數庫時要使用編譯器指令的“-lpthread”選項(pthread是共享庫檔案)
6.1 建立線程
注:建立出新線程後,新線程去執行函數,主線程繼續往下運作,誰先誰後不一定,同理fork父子程序
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
//參數:
//thread:線程id,将線程id存入,線程辨別符,線程棧的起始位址,輸出型參數
//attr:線程屬性,NULL,8種選項
//函數指針:新線程執行該函數指針指向的代碼,線程回調函數
//arg:傳遞給函數指針指向的函數的參數,線程回調函數的參數
//傳回值:成功傳回0,失敗傳回錯誤碼,如:
//EAGAIN 描述: 超出了系統限制,如建立的線程太多。
//EINVAL 描述: tattr 的值無效。
例:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
void* thread_func(void* arg)
{
(void)arg;
while(1)
{
printf("I am new threadn");
sleep(1);
}
}
int main()
{
pthread_t tid;
if ( pthread_create(&tid,NULL,thread_func,NULL) == -1){
perror("pthread_create");
exit(1);
}
while(1){
printf("I am main threadn");
sleep(1);
}
return 0;
}
結果:
6.2 線程ID
線程是程序的一個執行分支,并且線程在核心中的存在狀态是輕量級程序,是以線程ID和程序ID存在一定的關系,可檢視:linux下的線程ID和程序ID
6.3 線程的檢視以及多線程的調試
線程是程序的執行分支,多個線程共享同一塊虛拟位址空間,在這其中,多個線程共享資料段、 代碼段等等,但還是存在自己私有的一些結構,對于這些結構,我們可以通過不同的方法可以進行檢視。可參考:線程的檢視以及多線程的調試
6.4 等待線程——pthread_join
功能:以阻塞的方式回收新線程,可以得到線程的退出碼,并回收其資源
如果不使用pthread_join回收線程,有可能造成和僵屍程序一樣的問題,造成記憶體洩漏;
#include <pthread.h>
int pthread_join(pthread_t thread, //要等待的線程ID
void **retval);//用于接收線程退出的退出碼
等待線程的目的:
保證線程的退出順序:保證一個線程退出并且回收資源後允許下一個程序退出
回收線程退出時的資源情況:保證目前線程退出後,建立的新線程不會複用剛才退出線程的位址空間
獲得新線程退出時的結果是否正确的退出傳回值,這個有點類似回收僵屍程序的wait,保證不會發生記憶體洩露等問題
6.5 終止線程
方式1:在一個線程中return
如果線程通過return傳回,那麼retval所指向的單元裡存放的是tread函數的傳回值
例:main函數建立一個新線程,新線程執行完自己的函數,使用return退出,那麼傳回值就是退出碼
方式2:exit
如果線程是自己調用exit終止的,那麼就是直接退出,并且exit表示程序的退出
exit和return的差別:
return是函數的退出,exit是程序的退出
return執行結束後會調用exit或和exit類似的函數,return會釋放局部變量并且彈出棧桢,回到上一個函數繼續執行
方式3: 使用pthread_exit()
線程自己調用函數終止,pthread_ jion()函數裡的retval(退出碼)就是pthread_exit的參數
#include <pthread.h>
void pthread_exit(void *retval);
線程的退出函數,retval是退出碼,該函數隻是目前線程退出,不影響其他線程
方式4:
如果線程是通過pthread_ cancel被别的線程異常終止,則retval(退出碼)就是PTHREAD_ CANCELED
#include <pthread.h>
int pthread_cancel(pthread_t thread);//線程ID
//可以終止自己的線程也可以終止别人的線程
//成功傳回0,失敗傳回-1
//終止自己算是成功退出,發回0,終止别的程序,則那個程序算是失敗退出,傳回-1;
//不是立馬執行,會延遲一會,等到cancel的時間點
//系統調用的都是cancel點
例1:使用return退出
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
void* thread_func(void* arg)
{
(void)arg;
int count = 0;
while(1)
{
++count;
if(count > 10)
return NULL;//使用return方式退出
printf("I am new thread:%d,count: %dn",pthread_self(),count);
sleep(1);
}
}
int main()
{
pthread_t tid;
if ( pthread_create(&tid,NULL,thread_func,NULL) == -1){
perror("pthread_create");
exit(1);
}
while(1){
printf("I am main thread:%dn",pthread_self());
sleep(1);
}
return 0;
}
結果:
例2:exit退出
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
void* thread_func(void* arg)
{
(void)arg;
int count = 0;
while(1)
{
++count;
if(count > 10)
exit(1);//使用return方式退出
printf("I am new thread:%d,count: %dn",pthread_self(),count);
sleep(1);
}
}
int main()
{
pthread_t tid;
if ( pthread_create(&tid,NULL,thread_func,NULL) == -1){
perror("pthread_create");
exit(1);
}
while(1){
printf("I am main thread:%dn",pthread_self());
sleep(1);
}
return 0;
}
結果:
例3:pthread_exit退出
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
void* thread_func(void* arg)
{
(void)arg;
int count = 0;
while(1)
{
++count;
if(count > 10)
pthread_exit(NULL);
printf("I am new thread:%d,count: %dn",pthread_self(),count);
sleep(1);
}
}
int main()
{
pthread_t tid;
if ( pthread_create(&tid,NULL,thread_func,NULL) == -1){
perror("pthread_create");
exit(1);
}
while(1){
printf("I am main thread:%dn",pthread_self());
sleep(1);
}
return 0;
}
結果:
例4:pthread_cancel
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
void* thread_func(void* arg)
{
(void)arg;
while(1)
{
printf("I am new thread:%dn",pthread_self());
sleep(1);
}
}
int main()
{
pthread_t tid;
if ( pthread_create(&tid,NULL,thread_func,NULL) == -1){
perror("pthread_create");
exit(1);
}
int count = 0;
void* ret;
while(1){
++count;
if(count > 5){
pthread_cancel(tid);
pthread_join(tid,&ret);
}
if(ret == PTHREAD_CANCELED)
printf("thread is return,thread id: %d, return code:PTHREAD_CANCELEDn",tid);
else
printf("thread is return,thread id: %d, return code:NULLn",tid);
printf("I am main thread: %d,count: %dn",pthread_self(),count);
sleep(1);
}
return 0;
}
結果:
6.6 線程分離
6.6.1 線程的兩種狀态——可結合、可分離
線程分為兩種狀态:可結合态和分離态;預設情況下,線程被建立成可結合的。
1. 可結合态:
這種狀态下的線程是能夠被其他程序回收其資源或殺死的,這句話我的了解是:與其說它能夠被其他程序回收或殺死,不如說它需要被其他程序回收或殺死;當它在被其他線程回收之前,它的存儲器資源(如棧)是不會釋放的;
這跟子程序很相似,如果不用父程序wait回收的話,就會變成僵屍程序同理,如果一個可結合态線程不用pthread_join回收,則會變成類似僵屍程序
2. 分離态
這種狀态下的線程是不能夠被其他線程回收或殺死的;它的存儲資源在它終止時由系統自動釋放
為了避免存儲器洩漏,每個可結合線程需要顯示的調用pthread_join回收;>要麼就将其變成分離态的線程
6.6.2 線程分離函數—–pthread_detach
#include <pthread.h>
int pthread_detach(pthread_t thread);
//将pthread_thread對應的線程設為分離态的線程
pthread_detach的兩種用法:
新線程中寫:pthread_detach(pthread_self());
主線程中寫:pthread_detach(thread);
注:多個線程,是在同一個程序中的,它們都共享着同一塊虛拟空間,第一種方法是将自己從這些線程中分離出來;
第二種方法是将指定的線程從這些線程中分離出去;簡單來說就是,一個是自己把自己弄出去,一個是讓别人把自己弄出去