程序概念
程序是表示資源配置設定的基本機關,又是排程運作的基本機關。例如,使用者運作自己的程式,系統就建立一個程序,并為它配置設定資源,包括各種表格、記憶體空間、磁盤空間、I/O裝置等。然後,把該程序放人程序的就緒隊列。程序排程程式選中它,為它配置設定CPU以及其它有關資源,該程序才真正運作。是以,程序是系統中的并發執行的機關。
在Mac、Windows NT等采用微核心結構的作業系統中,程序的功能發生了變化:它隻是資源配置設定的基本機關,而不再是排程運作的機關。在微核心系統中,真正排程運作的基本機關是線程。是以,實作并發功能的機關是線程。
線程概念
線程是程序中執行運算的最小機關,亦即執行處理機排程的基本機關。如果把程序了解為在邏輯上作業系統所完成的任務,那麼線程表示完成該任務的許多可能的子任務之一。線程可以在處理器上獨立排程執行,這樣,在多處理器環境下就允許幾個線程各自在單獨處理器上進行。作業系統提供線程就是為了友善而有效地實作這種并發性。
相對程序而言,線程是一個更加接近于執行體的概念,它可以與同程序中的其他線程共享資料,但擁有自己的棧空間,擁有獨立的執行序列。在串行程式基礎上引入線程和程序是為了提高程式的并發度,進而提高程式運作效率和響應時間。
引入線程的好處
(1)易于排程。
(2)提高并發性。通過線程可友善有效地實作并發性。程序可建立多個線程來執行同一程式的不同部分。
(3)開銷少。建立線程比建立程序要快,所需開銷很少。。
(4)利于充分發揮多處理器的功能。通過建立多線程程序(即一個程序可具有兩個或更多個線程),每個線程在一個處理器上運作,進而實作應用程式的并發性,使每個處理器都得到充分運作。
程序:
子程序具備自己獨立的使用者空間(内容全部複制父程序);
父子程序不可互相通路對方資源;
線程:
僅申請自己的棧空間,與同程序的其它線程共享記憶體空間;
需要注意資源的同步和互斥通路問題
在Linux系統中,多線程的管理使用 pthread_t
線程程序基本操作
Linux多線程操作pthread_t程式概念線程概念線程程式基本操作 一、建立線程 pthread_create
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiNx8FesU2cfdGLwczX0xiRGZkRGZ0Xy9GbvNGLwIzXlpXazxSPJhFZ2AHWZlnVtRVQClGVF5UMR9Fd4VGdsATNfd3bkFGazxycykFaKdkYzZUbapXNXlleSdVY2pESa9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLyImMmFzY2QWZzUmYxUTM4IjZlRDO1kTMlNGO0gzNjNzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)
第一個參數為指向線程辨別符的指針,也就是線程對象的指針
第二個參數用來設定線程屬性。
第三個參數是線程運作函數的位址,通俗了解線程要執行函數(線程做的事情的)指針。一般這個函數執行時間比較長(有while循環),做的事情比較多。如果單次動作(執行時間比較短),也就無需多線程執行了。
最後一個參數是線程要運作函數的參數。
線程的預設堆棧大小是1MB,就是說,系統每建立一個線程就要至少提供1MB的記憶體,那麼,建立線程失敗,極有可能就是記憶體不夠用了。
pthread_create會導緻記憶體洩露! pthread_create建立的線程結束後,系統并未回收其資源,進而導緻了洩露。
為什麼要分離線程?
線程的分離狀态決定一個線程以什麼樣的方式來終止自己。線程的預設屬性是非分離狀态,這種情況下,原有的線程等待建立的線程結束。隻有當pthread_join()函數傳回時,建立的線程才算終止,才能釋放自己占用的系統資源。而分離線程不是這樣子的,它沒有被其他的線程所等待,自己運作結束了,線程也就終止了,馬上釋放系統資源。程式員應該根據自己的需要,選擇适當的分離狀态。
從上面的描述中可以得知如果調用pthread_create函數建立一個預設非分離狀态的線程,如果不用pthread_join()函數,線程結束時并不算終止,是以仍然會占用系統資源。這裡有如下幾種方法解決這個問題:
如果新線程建立後,不用pthread_join()等待回收新線程,那麼就會造成記憶體洩漏,但是當等待新線程時,主線程就會一直阻塞,影響主線程處理其他連結要求,這時候就需要一種辦法讓新線程退出後,自己釋放所有資源,是以産生了線程分離。
1.使用pthread_join()函數回收相關記憶體區域。
int pthread_detach(pthread_t thread);将已經運作中的線程設定為分離狀态;
pthread_t tid;
void* state;
pthread_create(&tid, NULL, test, NULL);
pthread_join(tid, &state);
pthread_join使一個線程等待另一個線程結束。代碼中如果沒有pthread_join 主線程會很快結束進而使整個程序結束,進而使建立的線程沒有機會開始執行就結束了。加入pthread_join後,主線程會一直等待直到等待的線程結束自己才結束,使建立的線程有機會執行。pthread_join()函數以阻塞的方式等待thread指定的線程結束。當函數傳回時,被等待線程的資源被收回。如果線程已經結束,那麼該函數會立即傳回。并且thread指定的線程必須是joinable的。
2.可以調用 pthread_detach() 函數分離線程。
pthread_t tid;
pthread_create(&tid, NULL, test, NULL);
pthread_detach(tid);
當然,也可以在 thread function 中調用。
void* test(void* arg)
{
.....
pthread_detach(pthread_self());
return NULL;
}
3.使用線程屬性 pthread_attr_setdetachstate
pthread_attr_t attr;
pthread_t tid;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, test, NULL);
sleep(3);//等待線程結束
pthread_attr_destroy(&attr);
二、線程屬性
pthread_create()中的attr參數是一個結構指針,結構中的元素分别對應着新線程的運作屬性,主要包括以下幾項:
typedef struct
{
int detachstate; //線程的分離狀态
int schedpolicy; //線程排程政策
struct sched_param schedparam; //線程的排程參數
int inheritsched; //線程的繼承性
int scope; //線程的作用域
size_t guardsize; //線程棧末尾的警戒緩沖區大小
int stackaddr_set;
void * stackaddr; //線程棧的位置
size_t stacksize; //線程棧的大小
}pthread_attr_t;
__detachstate, 表示新線程是否與程序中其他線程脫離同步,預設為 PTHREAD_CREATE_JOINABLE狀态。如果置位,則新線程不能用pthread_join()來同步,且在退出時自行釋放所占用的資源。這個屬性也可以線上程建立并運作以後用pthread_detach()來設定,而一旦設定為 PTHREAD_CREATE_DETACH狀态(不論是建立時設定還是運作時設定)則不能再恢複到 PTHREAD_CREATE_JOINABLE狀态。
int pthread_attr_setdetachstate (pthread_attr_t *__attr, int __detachstate)設定線程的分離狀态
int pthread_attr_getdetachstate (const pthread_attr_t *__attr,int *__detachstate)擷取線程參數中的分離狀态;
__schedpolicy,表示新線程的排程政策,主要包括 SCHED_OTHER(正常、非實時)、SCHED_RR(實時、輪轉法)和 SCHED_FIFO(實時、先入先出)三種,預設為SCHED_OTHER,後兩種排程政策僅對超級使用者有效。運作時可以用過 pthread_setschedparam()來改變。
int pthread_attr_setschedpolicy (pthread_attr_t *__attr, int __policy),設定線程的排程政策;
__schedparam,一個struct sched_param結構,目前僅有一個sched_priority整型變量表示線程的運作優先級。這個參數僅當排程政策為實時(即SCHED_RR 或SCHED_FIFO)時才有效,并可以在運作時通過pthread_setschedparam()函數來改變,預設為0。
int pthread_attr_getschedparam (const pthread_attr_t *__restrict __attr, struct sched_param *__restrict __param)
//擷取參數中的線程優先級;
int pthread_attr_setschedparam (pthread_attr_t *__restrict __attr, const struct sched_param *__restrict __param)
//設定線程的優先級;
__inheritsched, 有兩種值可供選擇:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新線程使用顯式指定排程政策和 排程參數(即attr中的值),而後者表示繼承調用者線程的值。預設為PTHREAD_EXPLICIT_SCHED
int pthread_attr_setinheritsched (pthread_attr_t *__attr, int __inherit)
//設定線程排程政策的繼承屬性,該函數必須在root權限下調用;
__scope, 表示線程間競争CPU的範圍,也就是說線程優先級的有效範圍。POSIX的标準中定義了兩個值: PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示與系統中所有線程一起競争CPU時間,後者表示僅與同 程序中的線程競争CPU。目前LinuxThreads僅實作了PTHREAD_SCOPE_SYSTEM一值。
int pthread_attr_setscope(pthread_attr_t *attr, int contentionscope);
//設定線程優先級的可競争範圍:
pthread_attr_t結構中還有一些值,但不使用pthread_create()來設定。
int pthread_attr_init (pthread_attr_t *__attr), 初始化pthread建立參數;
為了設定這些屬性,POSIX定義了一系列屬性設定函數,包括pthread_attr_init()、pthread_attr_destroy()和與各個屬性相關的pthread_attr_get---/pthread_attr_set---函數。
三、線程終止
線程退出
void pthread_exit(void *value_ptr); //線上程執行的函數中調用此接口
例子:
void *thread_run(void* arg)
{
while(1)
{
printf("new thread,thread is :%u,pid:%d\n",pthread_self(),getpid());
sleep(1);
pthread_exit(NULL);
}
}
線程取消
int pthread_cancel(pthread_t thread);
參數
thread:線程ID
傳回值:成功傳回0;失敗傳回錯誤碼
例子:
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,thread_run,NULL);
while(1)
{
printf("main thread,thread is :%u,pid:%d\n",pthread_self(),getpid()) ;
sleep(3);
pthread_cancel(pthread_self());//殺死自己,pthread_self()是擷取主線程id
}
return 0;
}
int pthread_setcancelstate(int state, int *oldstate)
state: 欲設定的線程狀态,PTHREAD_CANCEL_ENABLE和PTHREAD_CANCEL_DISABLE。預設值是PTHREAD_CANCEL_ENABLE,可以被取消。
oldstate: 存儲原來的線程狀态
int pthread_setcanceltype(int type, int *oldtype)
type: 欲設定的類型,PTHREAD_CANCEL_DEFERRED:在取消點取消和PTHREAD_CANCEL_ASYNCHRONOUS:可随時執行新的或未決的請求。
oldtype: 存儲原來的類型
四、線程安全
多個線程同時操作臨界資源而不會出現資料二義性,實作線程安全:
互斥: 臨界資源同一時間唯一通路,是指某一資源同時隻允許一個通路者對其進行通路,具有唯一性和排它性,操作的獨占性。但互斥無法限制通路者對資源的通路順序,即通路是無序的。
同步: 臨界資源的合理通路,是指在互斥的基礎上(大多數情況),通過其它機制實作通路者對資源的有序通路。顯然,同步是一種更為複雜的互斥,而互斥是一種特殊的同步。
多線程的同步與互斥的差別
假如把整條道路看成是一個【程序】的話,那麼馬路中間白色虛線分隔開來的各個車道就是程序中的各個【線程】了
①這些線程(車道)共享了程序(道路)的公共資源(土地資源)。
②這些線程(車道)必須依賴于程序(道路),也就是說,線程不能脫離于程序而存在(就像離開了道路,車道也就沒有意義了)。
③這些線程(車道)之間可以并發執行(各個車道你走你的,我走我的),也可以互相同步(某些車道在交通燈亮時禁止繼續前行或轉彎,必須等待其它車道的車輛通行完畢)。
④這些線程(車道)之間依靠代碼邏輯(交通燈)來控制運作,一旦代碼邏輯控制有誤(死鎖,多個線程同時競争唯一資源),那麼線程将陷入混亂,無序之中。
⑤這些線程(車道)之間誰先運作是未知的,隻有線上程剛好被配置設定到CPU時間片(交通燈變化)的那一刻才能知道。
1.互斥
互斥如何實作:
互斥鎖: 一個1/0的計數器
1辨別完成加鎖,加鎖就是計數-1;
操作完畢後要解鎖, 解鎖就是計數+1
0表示不可以加鎖, 不能加鎖則等待
互斥鎖的操作步驟:
1.定義互斥鎖變量 pthread_mutex_t
pthread_mutex_t lock;
2.初始化互斥鎖變量 pthread_mutex_init
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
3.加鎖 pthread_mutex_lock
int pthread_mutex_trylock(pthread_mutex_t* mutex);//非阻塞式加鎖
int pthread_mutex_lock(pthread_mutex_t* mutex)//阻塞式加鎖
如果一個線程既想獲得鎖,又不想挂起等待,可以調用pthread_mutex_trylock,如果Mutex已經被 另一個線程獲得,這個函數會失敗傳回EBUSY,而不會使線程挂起等待。
4.解鎖 pthread_mutex_unlock
int pthread_mutex_unlock(pthread_mutex_t* mutex)//解鎖
5.銷毀互斥鎖 pthread_mutex_destory
int pthread_mutex_destroy(pthread_mutex_t* mutex);
死鎖::對所資源的競争以及程序/線程加鎖的推進順序不當,因為對一些無法加鎖的鎖進行加鎖而導緻程式卡死
死鎖産生的四個必要條件:
互斥條件(我能操作别人不能操作)
不可剝奪操作(我的鎖,别人不能解)
請求與保持條件(拿着碗裡的,看着鍋裡的)
環路等待條件
避免死鎖:破壞必要條件
死鎖處理:死鎖檢測算法 ,銀行家算法
2.同步
條件變量:描述某些資源就緒與否的狀态,為了實作同步而引入。同步是以互斥為前提的。少數情況可實作無鎖同步。
1.定義條件變量 pthread_cond_t
pthread_cond_t condition;
2.初始化條件變量 pthread_cond_init
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
參數: pthread_cond_attr是用來設定pthread_cond_t的屬性,當傳入的值是NULL的時候表示使用預設的屬性。
3.等待 / 喚醒 pthread_cond_wait / pthread_cond_signal
int pthread_cond_broadcast(pthread_cond_t* cond);//喚醒條件變量等待的所有線程
int pthread_cond_signal(pthread_cond_t* cond)//喚醒條件變量上等待的一個線程
int pthread_cond_wait(pthread_cond_t* cond)//解鎖互斥量并等待條件變量觸發
int pthread_cond_timewait(pthread_cond_t* cond,int abstime)//pthread_cond_wait,但可設定等待逾時
4.銷毀條件變量 pthread_cond_destroy
int pthread_cond_destroy(pthread_cond_t* cond);
條件變量為什麼要搭配互斥鎖使用?
因為條件變量本身隻提供等待與喚醒的功能,具體要什麼時候等待需要使用者來進行判斷.這個條件的判斷,通常涉及臨界資源的操作(其他線程要通過修改條件,來促使條件滿足), 而這個臨界資源的操作應該受到保護.是以要搭配互斥鎖一起使用。
3.互斥和同步的聯合使用
pthread_mutex_lock(&mutex)
while or if(線程執行的條件是否成立)
pthread_cond_wait(&cond,&mutex);
線程執行
pthread_mutex_unlock(&mutex);
五、其他操作
程序id:
這裡所說的程序ID指我們通過fork建立子程序,子程序和父程序在核心中獨立運作,并且一個程序對應一個程序描述符(PCB),PCB中包含了程序的ID,通過getpid傳回目前程序ID
線程id:
核心态線程id:linux核心中,并不存線上程這一說,而是通過複制了程序的PCB作為辨別自己(線程),作為程序的一個執行分支;既然有程序描述符(PCB)辨別,自然就有一個辨別符(ID)來辨別着我是你(程序)的哪一個分支,這個辨別符(ID)就是核心中的線程ID,通過syscall獲得
#include <sys/syscall.h>
pid_t tid;
tid = syscall(SYS_gettid); //線上程執行的函數中調用此接口
使用者态線程id:對線程的操控是由使用者自己來完成,那麼對此線程操控,使用者知道你是哪一個線程,故此又有了使用者态的線程ID;這裡我們通過pthread_self()函數獲得。
#include <pthread.h>
pthread_t pthread_self(void); //線上程執行的函數中調用此接口
傳回值:成功傳回0,失敗傳回錯誤碼
注:這裡的ID是一個位址,而不是向上面兩個ID是一個整數
對于單線程的程序,核心中tid==pid,對于多線程程序,他們有相同的pid,不同的tid。tid用于描述核心真實的pid和tid資訊。
pthread_self傳回的是posix定義的線程ID,man手冊明确說明了和核心線程tid不同。它隻是用來區分某個程序中不同的線程,當一個線程退出後,新建立的線程可以複用原來的id。
描述線程的id,為什麼需要兩個不同的ID呢?
答:這是因為線程庫實際上由兩部分組成:核心的線程支援+使用者态的庫支援(glibc),Linux在早期核心不支援線程的時候glibc就在庫中(使用者态)以纖程(就是使用者态線程)的方式支援多線程了,POSIX thread隻要求了使用者程式設計的調用接口對核心接口沒有要求。linux上的線程實作就是在核心支援的基礎上以POSIX thread的方式對外封裝了接口,是以才會有兩個ID的問題。
gettid 擷取的是核心中真實線程ID, 對于多線程程序來說,每個tid實際是不一樣的。
而pthread_self擷取的是相對于程序的線程控制塊的首位址, 隻是用來描述統一程序中的不同線程
————————————————
版權聲明:本文為CSDN部落客「銀冰冷月」的原創文章,遵循CC 4.0 BY-SA版權協定,轉載請附上原文出處連結及本聲明。
原文連結:https://blog.csdn.net/sevens_0804/article/details/102823184