???? 博主 libin9iOak帶您 Go to New World.???
?? 個人主頁——libin9iOak的博客??
?? 《面試題大全》 文章圖文并茂??生動形象??簡單易學(xué)!歡迎大家來踩踩~??
?? 《IDEA開發(fā)秘籍》學(xué)會IDEA常用操作,工作效率翻倍~??
???? 希望本文能夠給您帶來一定的幫助??文章粗淺,敬請批評指正!????
第十章 線程與線程控制
學(xué)習(xí)目的
? 通過對線程與線程控制的相關(guān)知識點的編程學(xué)習(xí)和鍛煉,培養(yǎng)學(xué)生們對線程相關(guān)實例問題的分析與解決能力。
學(xué)習(xí)要求
? 了解:同一進程中的線程共享的資源。線程編程時存在的問題,進程與線程的比較,線程ID和線程是否相同的判斷。
理解:線程退出時的清理機制;
掌握:線程的創(chuàng)建、終止和取消,detach以及線程屬性。
學(xué)習(xí)方法
? 本章的線程概念較為抽象,需要學(xué)生較強的抽象思維能力。多線程編程部分需要學(xué)生上機實踐。
概念和原理
10.1 線程概述
10.1.1 線程的引入
? 由于進程是一個資源的擁有者,因此在創(chuàng)建、撤銷和切換中,系統(tǒng)必須為此付出較大的時間和空間的開銷。
線程保留了并發(fā)的優(yōu)點,避免了進程的高代價
10.1.2 線程的共享問題
? 進程內(nèi)的所有線程共享進程的很多資源(這種共享又帶來了同步問題)。
? 線程間共享 線程私有
進程指令 線程ID
全局變量 寄存器集合(包括PC和棧指針)
打開的文件 棧(用于存放局部變量)
信號處理程序 信號掩碼
當(dāng)前工作目錄 優(yōu)先級
用戶ID
10.1.3 線程的數(shù)據(jù)共享
? 每個線程私有的數(shù)據(jù)和資源:線程ID、線程上下文(一組寄存器值的集合)、線程局部變量(存儲在棧中)。
10.1.4 線程的互斥問題
? 對全局變量進行訪問的基本步驟
a) 將內(nèi)存單元中的數(shù)據(jù)讀入寄存器
b) 對寄存器中的值進行運算
c) 將寄存器中的值寫回內(nèi)存單元
10.2 線程和進程的比較
10.2.1 線程和進程的比較
(1) 調(diào)度
在傳統(tǒng)的操作系統(tǒng)中,進程作為擁有資源和獨立調(diào)度、分派的基本單位。而在引入線程的操作系統(tǒng)中,則把線程作為調(diào)度和分派的基本單位,而進程作為資源擁有的基本單位。
(2) 并發(fā)性
在引入線程的操作系統(tǒng)中,不僅進程之間可以并發(fā)執(zhí)行,而且在一個進程中的多個線程之間亦可并發(fā)執(zhí)行,使得操作系統(tǒng)具有更好的并發(fā)性,從而能更加有效地提高系統(tǒng)資源的利用率和系統(tǒng)的吞吐量。
(3) 擁有資源
一般而言,線程自己不擁有系統(tǒng)資源(也有一點必不可少的資源),但它可以訪問其隸屬進程的資源,即一個進程的代碼段、數(shù)據(jù)段及所擁有的系統(tǒng)資源,如已打開的文件、I/O 設(shè)備等,可以供該進程中的所有線程所共享。
(4) 獨立性
同一進程中的不同線程共享進程的內(nèi)存空間和資源。
同一進程中的不同線程的獨立性低于不同進程。
(5) 系統(tǒng)開銷
線程的切換只需要保存和設(shè)置少量的寄存器內(nèi)容,不涉及存儲器管理方面的操作。
(6) 支持多處理機系統(tǒng)
一個進程分為多個線程分配到多個處理機上并行執(zhí)行,可加速進程的完成。
10.2.2 線程的屬性
(1) 輕型實體
線程自己基本不擁有系統(tǒng)資源,只擁有少量必不可少的資源:TCB,程序計數(shù)器、一組寄存器、棧。
(2) 獨立調(diào)度和分派的基本單位
在多線程OS中,線程是獨立運行的基本單位,因而也是獨立調(diào)度和分派的基本單位。
(3) 可并發(fā)執(zhí)行
同一進程中的多個線程之間可以并發(fā)執(zhí)行,一個線程可以創(chuàng)建和撤消另一個線程。
(4) 共享進程資源
它可與同屬一個進程的其它線程共享進程所擁有的全部資源。
10.3 線程的狀態(tài)與組成
10.3.1 線程的狀態(tài)
? 線程運行時有以下3種狀態(tài):
- 執(zhí)行狀態(tài):表示線程正獲得CPU而運行;
- 就緒狀態(tài):表示線程已具備了各種運行條件,一旦獲得CPU便可執(zhí)行;
- 阻塞狀態(tài):表示線程在運行中因某事件而受阻,處于暫停執(zhí)行的狀態(tài);
圖10-1 線程的狀態(tài)轉(zhuǎn)換
10.3.2 線程的組成
線程必須在某個進程內(nèi)執(zhí)行
一個進程可以包含一個線程或多個線程
圖10-2 單線程和多線程的進程模型
? 每個線程有一個TCB結(jié)構(gòu),即線程控制塊,用于保存自己私有的信息,主要由以下部分組成:
? 一個唯一的線程標(biāo)識符
? 一組寄存器 :包括程序計數(shù)器、狀態(tài)寄存器、通用寄存器的內(nèi)容;
? 線程運行狀態(tài):用于描述線程正處于何種運行狀態(tài);
? 優(yōu)先級:描述線程執(zhí)行的優(yōu)先程度;
? 線程專有存儲器:用于保存線程自己的局部變量拷貝;
? 信號屏蔽:對某些信號加以屏蔽。
? 兩個棧指針:核心棧、用戶棧
(1) 線程ID
同進程一樣,每個線程也有一個線程ID
進程ID在整個系統(tǒng)中是唯一的,線程ID只在它所屬的進程環(huán)境中也是唯一的。
線程ID的類型是pthread_t,在Linux中的定義如下:
? typedef unsigned long int pthread_t
(/usr/include/bits/pthreadtypes.h)
(2) 獲取線程ID
pthread_self函數(shù)可以讓調(diào)用線程獲取自己的線程ID
函數(shù)原型
? 頭文件:pthread.h
? pthread_t pthread_self();
返回調(diào)用線程的線程ID
(3) 比較線程ID
Linux中使用整型表示線程ID,而其他系統(tǒng)則不一定
FreeBSD 5.2.1、Mac OS X 10.3用一個指向pthread結(jié)構(gòu)的指針來表示pthread_t類型。
為了保證應(yīng)用程序的可移植性,在比較兩個線程ID是否相同時,建議使用pthread_equal函數(shù)
(4) pthread_equal函數(shù)
該函數(shù)用于比較兩個線程ID是否相同
函數(shù)原型
? 頭文件:pthread.h
? int pthread_equal(pthread_t tid1, pthread_t tid2);
若相等則返回非0值,否則返回0
(5) 進程/線程控制操作對比
應(yīng)用功能 | 線程 | 進程 |
---|---|---|
創(chuàng)建 | pthread_create | fork,vfork |
退出 | pthread_exit | exit |
等待 | pthread_join | wait、waitpid |
取消/終止 | pthread_cancel | abort |
讀取ID | pthread_self() | getpid() |
同步互斥/通信機制 | 互斥鎖、條件變量、讀寫鎖 | 無名管道、有名管道、信號、消息隊列、信號量、共享內(nèi)存 |
10.4 線程的創(chuàng)建與終止
10.4.1 線程的創(chuàng)建
? 在多線程OS環(huán)境下,應(yīng)用程序在啟動時,通常僅有一個“初始化線程”線程在執(zhí)行。
? 在創(chuàng)建新線程時,需要利用一個線程創(chuàng)建函數(shù)(或系統(tǒng)調(diào)用),并提供相應(yīng)的參數(shù)。
- 如指向線程主程序的入口指針、堆棧的大小,以及用于調(diào)度的優(yōu)先級等。
? 在線程創(chuàng)建函數(shù)執(zhí)行完后,將返回一個線程標(biāo)識符供以后使用
? Linux下線程創(chuàng)建
- Linux系統(tǒng)下的多線程遵循POSIX線程接口,稱為pthread。
#include <pthread.h>
int pthread_create(
? pthread_t *restrict tidp, //指向線程標(biāo)識符的指針
? const pthread_attr_t *restrict attr, //設(shè)置線程屬性
? void *(*start_rtn)(void), //線程運行函數(shù)的起始地址
? void *restrict arg; ) //運行函數(shù)的參數(shù)
10.4.2 線程的終止
? 線程完成了自己的工作后自愿退出;
? 或線程在運行中出現(xiàn)錯誤或由于某種原因而被其它線程強行終止。
? 終止線程的方式有兩種:
- 自愿退出 return , pthread_exit
void pthread_exit(void *rval_ptr);
由于pthread庫不是Linux系統(tǒng)默認的庫,連接時需要使用庫libpthread.a,所以如果使用pthread_create、pthread_exit等函數(shù)時,在編譯中要加-lpthread參數(shù):
#gcc -o XXX -lpthread XXX.c
- 強行終止 pthread_cancel
? 父線程等待子線程終止
- 函數(shù)原型
- 頭文件:pthread.h
- int pthread_join(pthread_t thread,void **rval_ptr);
- thread:需要等待的子線程ID
- rval_ptr:(若不關(guān)心線程返回值,可直接將該參數(shù)設(shè)置為空指針NULL)
- 若線程從啟動線程通過return返回,rval_ptr指向的內(nèi)存單元中存放的是線程的返回值
- 若線程被其它線程調(diào)用pthread_cancel取消,rval_ptr指向的內(nèi)存單元存放常數(shù)PTHREAD_CANCELED
- 若線程通過自己調(diào)用pthread_exit函數(shù)終止,rval_ptr就是調(diào)用pthread_exit時傳入的參數(shù)
- 調(diào)用該函數(shù)的父線程將一直被阻塞,直到指定的子線程終止
- 返回值
- 成功返回0,否則返回錯誤編號
? 取消線程
- 線程調(diào)用該函數(shù)可以取消同一進程中的其他線程(即讓該線程終止)
- 函數(shù)原型
- 頭文件: pthread.h
- int pthread_cancel(pthread_t tid);
- 參數(shù)與返回值
- tid:需要取消的線程ID
- 成功返回0, 出錯返回錯誤編號
? 線程清理處理函數(shù)
- 線程清理處理函數(shù)的注冊
- 頭文件:pthread.h
- void pthread_cleanup_push(void (*rtn)(void *), void *arg);
- void pthread_cleanup_pop(int execute);
- 參數(shù)
- rtn:清理函數(shù),無返回值,包含一個類型為指針的參數(shù)
- arg:當(dāng)清理函數(shù)被調(diào)用時,arg將被傳遞給清理函數(shù)
10.5 線程的屬性
(1) 線程屬性
圖10-3 POSIX規(guī)定的一些線程屬性
(2) 初始化和銷毀
? 函數(shù)原型
#include<pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
? 參數(shù)與返回值
- 成功返回0,否則返回錯誤編號
- attr:線程屬性,確保attr指向的存儲區(qū)域有效
- 為了移植性,pthread_attr_t結(jié)構(gòu)對應(yīng)用程序是不可見的,應(yīng)使用設(shè)置和查詢等函數(shù)訪問屬性
(3) 初始化線程屬性對象
屬性 | 缺省值 | 描述 |
---|---|---|
scope | PTHREAD_SCOPE_PROCESS | 新線程與進程中的其他線程發(fā)生競爭 |
detachstate | PTHREAD_CREATE_JOINABLE | 線程可以被其它線程等待 |
stackaddr | NULL | 新線程具有系統(tǒng)分配的棧地址 |
stacksize | 0 | 新線程具有系統(tǒng)定義的棧大小 |
priority | 0 | 新線程的優(yōu)先級為0 |
inheritsched | PTHREAD_EXPLICIT_SCHED | 新線程不繼承父線程調(diào)度優(yōu)先級 |
schedpolicy | SCHED_OTHER | 新線程使用優(yōu)先級調(diào)用策略 |
(4) 獲取線程棧屬性
? 函數(shù)原型
#include<pthread.h>
int pthread_attr_getstack(
? const pthread_attr_t *attr,
? void **stackaddr, size_t *stacksize);
? 參數(shù)與返回值
- attr:線程屬性
- stackaddr:該函數(shù)返回的線程棧的最低地址
- stacksize:該函數(shù)返回的線程棧的大小
- 成功返回0,否則返回錯誤編號
(5) 設(shè)置線程棧屬性
? 函數(shù)原型
#include<pthread.h>
int pthread_attr_setstack(
? const pthread_attr_t *attr,
? void *stackaddr, size_t *stacksize);
? 當(dāng)用完線程棧時,可以再分配內(nèi)存,并調(diào)用本函數(shù)設(shè)置新建棧的位置
? 參數(shù)與返回值
- attr:線程屬性
- stackaddr:新棧的內(nèi)存單元的最低地址,通常是棧的開始位置;對于某些處理器,棧是從高地址向低地址方向伸展的,stackaddr就是棧的結(jié)尾
- stacksize:新棧的大小
- 成功返回0,否則返回錯誤編號
(6) pthread_detach函數(shù)
? 函數(shù)原型
- 頭文件:pthread.h
- int pthread_detach(pthread_t tid);
? 參數(shù)與返回值
- tid:進入分離狀態(tài)的線程的ID
- 成功返回0,出錯返回錯誤編號
10.6 死鎖
10.6.1 死鎖的定義
如果一組進程中的每一個進程都在等待僅由該組進程中的其它進程才能引發(fā)的事件,那么該組進程是死鎖的。
10.6.2 產(chǎn)生死鎖的原因和必要條件
? 原因
a) 競爭不可搶占性資源引起死鎖
b) 競爭臨時性(消耗性)資源引起進行死鎖
c) 進程推進順序不當(dāng)引起死鎖
? 必要條件
a) 互斥條件 :進程對分配到的資源進行排它性使用。
b) 請求和保持條件 :進程已經(jīng)保持了至少一個資源,但又提出了新的資源要求,而該資源又被其他進程占有,請求進程阻塞,但對已經(jīng)獲得的資源不釋放。
c) 不剝奪條件 :進程已獲得的資源,使用完之前不能被剝奪,只能用完自己釋放。
d) 環(huán)路等待條件 :發(fā)生死鎖時,必然存在進程—資源的環(huán)形鏈。
10.6.3 處理死鎖的基本方法
(1) 預(yù)防死鎖:設(shè)置某些限制條件,破壞四個必要條件(除第一個互斥條件外的其他條件)中的一個或幾個。
優(yōu)點:容易實現(xiàn)。
缺點:系統(tǒng)資源利用率和吞吐量降低。
(2) 避免死鎖:在資源的動態(tài)分配過程用某種方法防止系統(tǒng)進入不安全狀態(tài)。
優(yōu)點:較弱限制條件可獲得較高系統(tǒng)資源利用率和吞吐量。
缺點:有一定實現(xiàn)難度。
(3) 檢測死鎖:預(yù)先不采取任何限制,也不檢查系統(tǒng)是否已進入不安全區(qū),通過設(shè)置檢測機構(gòu),檢測出死鎖后解除。
(4) 解除死鎖:常用撤消或掛起一些進程,回收一些資源。
10.7 線程間的同步和互斥
? 為使系統(tǒng)中的多線程能有條不紊的運行,系統(tǒng)必須提供用于實現(xiàn)線程間同步和互斥的機制。在多線程OS中,通常提供多種同步機制:
10.7.1 互斥鎖
? 互斥鎖可以有兩種狀態(tài), 即開鎖(unlock)和關(guān)鎖(lock)狀態(tài)
? 對互斥量進行加鎖以后,任何其他試圖再次對互斥量加鎖的線程都會被阻塞直到當(dāng)前線程釋放該互斥鎖。
? Linux中的線程互斥鎖
- int pthread_mutex_lock(pthread_mutex_t *mutex);
//返回時,互斥鎖已被鎖定。該線程使互斥鎖鎖住。如果互斥鎖已被另一個線程鎖定和擁有,則該線程將阻塞,直到互斥鎖變?yōu)榭捎脼橹埂?/p>
- int pthread_mutex_unlock(pthread_mutex_t *mutex);
//釋放互斥鎖,與pthread_mutex_lock成對存在。
10.7.2 條件變量
? 單純的互斥鎖用于短期鎖定,主要是用來保證對臨界區(qū)的互斥進入。而條件變量則用于線程的長期等待, 直至所等待的資源成為可用的。
? 使用步驟:
- 線程首先對mutex執(zhí)行關(guān)鎖操作,若成功便進入臨界區(qū),然后查找用于描述資源狀態(tài)的數(shù)據(jù)結(jié)構(gòu),以了解資源的情況。
- 只要發(fā)現(xiàn)所需資源R正處于忙碌狀態(tài),線程便轉(zhuǎn)為等待狀態(tài),并對mutex執(zhí)行開鎖操作后,等待該資源被釋放;
- 若資源處于空閑狀態(tài),表明線程可以使用該資源,于是將該資源設(shè)置為忙碌狀態(tài),再對mutex執(zhí)行開鎖操作。
10.7.3 信號量機制
(1) 私用信號量
? 當(dāng)某線程需利用信號量來實現(xiàn)同一進程中各線程之間的同步時,可調(diào)用創(chuàng)建信號量的命令來創(chuàng)建一私用信號量,其數(shù)據(jù)結(jié)構(gòu)存放在應(yīng)用程序的地址空間中。
(2) 公用信號量
? 公用信號量是為實現(xiàn)不同進程間或不同進程中各線程之間的同步而設(shè)置的。
10.8 Linux下的多線程編程
10.8.1 Linux下的多線程編程
(1) 多線程編程實例
#include <stdio.h>
#include <pthread.h>
void thread(void)
{
int i;
for(i=0;i<3;i++)
printf(“This is a pthread.\n”);
}
int main(void)
{
pthread_t id;
int i,ret;
ret=pthread_create(&id,NULL,(void *) thread,NULL);
if(ret!=0){
printf (“Create pthread error!\n”);
exit (1);
}
for(i=0;i<3;i++)
printf(“This is the main process.\n”);
pthread_join(id,NULL);
return (0);
}
執(zhí)行:gcc example.c -lpthread -o example
-l參數(shù)用于指定編譯時要用到的庫
(2) 線程標(biāo)識符 pthread_t
用來標(biāo)識一個線程。
(3) 線程創(chuàng)建函數(shù)pthread_create
函數(shù)原型:
int pthread_create (pthread_t * thread_id, __const pthread_attr_t * __attr, void (__start_routine) (void *),void *__restrict __arg)
- 第一個參數(shù)為指向線程標(biāo)識符的指針
- 第二個參數(shù)用來設(shè)置線程屬性
- 第三個參數(shù)是線程運行函數(shù)的起始地址
- 最后一個參數(shù)是運行函數(shù)的參數(shù)
- 函數(shù)thread不需要參數(shù),所以最后一個參數(shù)設(shè)為空指針。第二個參數(shù)也設(shè)為空指針,這樣將生成默認屬性的線程。
- 當(dāng)創(chuàng)建線程成功時,函數(shù)返回0,若不為0則說明創(chuàng)建線程失敗,常見的錯誤返回代碼為EAGAIN和EINVAL。前者表示系統(tǒng)限制創(chuàng)建新的線程,例如線程數(shù)目過多了;后者表示第二個參數(shù)代表的線程屬性值非法。
(4) pthread_join函數(shù)
函數(shù)原型:
int pthread_join (pthread_t __th, void **__thread_return)
- 第一個參數(shù)為被等待的線程標(biāo)識符 。
- 第二個參數(shù)為一個用戶定義的指針,用來存儲被等待線程返回值。
- 這個函數(shù)是一個線程阻塞的函數(shù),調(diào)用它的函數(shù)將一直等待到被等待的線程結(jié)束為止,當(dāng)函數(shù)返回時,被等待線程的資源被收回。
(5) pthread_exit函數(shù)
函數(shù)原型:
void pthread_exit (void *__retval)
- 唯一的參數(shù)是函數(shù)的返回代碼 。如果pthread_join中的第二個參數(shù)thread_return不是NULL,這個值將被傳遞給 thread_return。
- 需要注意的是:一個線程不能被多個線程等待,否則第一個接收到信號的線程成功返回,其余調(diào)用pthread_join的線程則返回錯誤代碼ESRCH。
(6) 互斥鎖
互斥鎖用來保證一段時間內(nèi)只有一個線程在執(zhí)行一段代碼。
重點
(1)線程清理機制;2)線程的屬性。
這部分內(nèi)容采用示例程序展示的方式教學(xué),通過針對性的編寫示例程序展示這些函數(shù)的使用,以及相應(yīng)功能的實現(xiàn)。同時通過實驗強化這部分知識的掌握。
難點
Linux多線程編程。
習(xí)題
1.比較線程和進程的區(qū)別。
答:(1) 調(diào)度
在傳統(tǒng)的操作系統(tǒng)中,進程作為擁有資源和獨立調(diào)度、分派的基本單位。而在引入線程的操作系統(tǒng)中,則把線程作為調(diào)度和分派的基本單位,而進程作為資源擁有的基本單位。
(2) 并發(fā)性
在引入線程的操作系統(tǒng)中,不僅進程之間可以并發(fā)執(zhí)行,而且在一個進程中的多個線程之間亦可并發(fā)執(zhí)行,使得操作系統(tǒng)具有更好的并發(fā)性,從而能更加有效地提高系統(tǒng)資源的利用率和系統(tǒng)的吞吐量。
(3) 擁有資源
一般而言,線程自己不擁有系統(tǒng)資源(也有一點必不可少的資源),但它可以訪問其隸屬進程的資源,即一個進程的代碼段、數(shù)據(jù)段及所擁有的系統(tǒng)資源,如已打開的文件、I/O 設(shè)備等,可以供該進程中的所有線程所共享。
(4) 獨立性
同一進程中的不同線程共享進程的內(nèi)存空間和資源。
同一進程中的不同線程的獨立性低于不同進程。
(5) 系統(tǒng)開銷
線程的切換只需要保存和設(shè)置少量的寄存器內(nèi)容,不涉及存儲器管理方面的操作。
(6) 支持多處理機系統(tǒng)
一個進程分為多個線程分配到多個處理機上并行執(zhí)行,可加速進程的完成。
2.死鎖產(chǎn)生的主要原因有哪些?
答:a) 競爭不可搶占性資源引起死鎖
b) 競爭臨時性(消耗性)資源引起進行死鎖
c) 進程推進順序不當(dāng)引起死鎖
3.死鎖的必要條件有哪些?
答:a) 互斥條件
b) 請求和保持條件
c) 不剝奪條件
d) 環(huán)路等待條件
- 如何解決死鎖?
答:(1) 預(yù)防死鎖:設(shè)置某些限制條件,破壞四個必要條件(除第一個互斥條件外的其他條件)中的一個或幾個。
(2) 避免死鎖:在資源的動態(tài)分配過程用某種方法防止系統(tǒng)進入不安全狀態(tài)。
(3) 檢測死鎖:預(yù)先不采取任何限制,也不檢查系統(tǒng)是否已進入不安全區(qū),通過設(shè)置檢測機構(gòu),檢測出死鎖后解除。
(4) 解除死鎖:常用撤消或掛起一些進程,回收一些資源。
原創(chuàng)聲明
=======
作者: [ libin9iOak ]
本文為原創(chuàng)文章,版權(quán)歸作者所有。未經(jīng)許可,禁止轉(zhuǎn)載、復(fù)制或引用。
作者保證信息真實可靠,但不對準(zhǔn)確性和完整性承擔(dān)責(zé)任。
未經(jīng)許可,禁止商業(yè)用途。
如有疑問或建議,請聯(lián)系作者。
感謝您的支持與尊重。文章來源:http://www.zghlxwxcb.cn/news/detail-512741.html
點擊
下方名片
,加入IT技術(shù)核心學(xué)習(xí)團隊。一起探索科技的未來,共同成長。文章來源地址http://www.zghlxwxcb.cn/news/detail-512741.html
到了這里,關(guān)于《Linux操作系統(tǒng)編程》 第十章 線程與線程控制: 線程的創(chuàng)建、終止和取消,detach以及線程屬性的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!