線程概念
Linux下沒有真正的線程,因爲Linux下的線程是用進程pcb模擬的(也就是說Linux下pcb實際上是一個線程),所以Linux下的線程也被叫做輕量級進程。既然Linux下pcb成了線程,那麼進程變成了線程組。
線程是進程的一條執行流,Linux下線程是以進程的pcb模擬的,所以才說Linux下的線程是輕量級進程,因此Linux下的線程是cpu調度的基本單位。
Linux下的進程成了線程組,進程id==線程組id,所以才說Linux下的進程是線程組,資源分配給整個線程組的,所以進程是資源分配的基本單位,並且進程中的線程共享大部分進程的資源。
線程共享
- 文件描述符表
- 用戶id、組id
- 信號處理方式
- 工作路徑(當前工作目錄)
- 共享虛擬地址空間——共享地址段和代碼段
線程獨有(相對獨有——因爲數據還是在虛擬地址空間)
- 棧區
- 上下文數據
- 線程id
- errno
- 信號屏蔽字
- 一組寄存器
線程的優缺點
優點:
linux下的線程共用進程的虛擬地址空間
- 線程的創建/銷燬成本更低——不用每次都創建虛擬地址空間
- 線程的調度切換成本更低
- 線程間的通信更加方便
- 線程的執行力度更加細緻
缺點:
- 缺乏訪問控制——線程安全
- 有些系統調用和程序異常時針對整個進程產生影響的
- 多個線程對臨界(公共)資源進行操作有可能會造成數據混亂
線程控制
線程創建
pthred_create
int pthred_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg)
thread //獲取線程id(用戶態線程id) 無符號長整形
attr //設置線程屬性,通常置空
start_routine //線程的入口函數(線程所運行的代碼)
arg //線程入口函數的參數
返回值 //成功返回0,失敗返回錯誤碼
通過參數返回一個用戶態的線程id,這個線程id是線程地址空間在進程虛擬地址空間的首地址,用戶態線程的操作都是圍繞這個用戶態的線程id來操作的。
每一個線程都是一個task_struck,也就意味着每個線程都有一個pid,但是ps命令只能顯示一個。task_struct中不僅有pid還有一個tgid(線程組id==線程組領導者的pid),那麼ps看到的pid實際上是這個tgid(線程組id),因此我們說linux下的進程稱爲的線程組。
查看線程信息可以使用 ps-L命令, LWP這一項顯示的就是線程pcb中的pid。
- tid 線程id
- pid 進程id
- tgid 線程組id(主線程的pid)
線程終止
不能再main函數中return,不能調用exit函數,因爲這兩個都是退出進程的,進程推出了,所有的線程都得退出。
pthread_exit
void pthread_exit(void *value_ptr) //退出調用線程
//value_ptr:value_ptr不要指向一個局部變量。
//返回值:無返回值,跟進程一樣,線程結束的時候無法返回到它的調用者(自身)
pthread_exit(NULL);
pthread_cancel
void pthread_cancel(pthread_t thread) //退出指定線程
//thread:線程ID
//返回值:成功返回0;失敗返回錯誤碼
pthread_cancel(tid);
線程等待
- 獲得其他普通線程的退出返回值,避免產生殭屍線程。
- 獲得退出線程的返回值,並且允許操作系統回收線程資源。
線程等待的前提: 只有線程處於joinable狀態(線程默認屬性),這個線程才能被等待。
pthrad_join
int pthrad_join(pthread_t thread, void **value_ptr)
//thread 線程id
//value_ptr 用於獲取一個線程退出的返回值
//阻塞性函數,如果線程沒有退出就一直等待
線程分離
默認情況下,新創建的線程是joinable狀態的,線程退出後,需要對其進行pthread_join操作,否則無法釋放資源,從而造成系統泄漏。 如果不關心線程的返回值,join是一種負擔,這個時候,我們可以告訴系統,當線程退出時,自動釋放線程
資源。
pthread_deatch
int pthread_detach(pthread_t thread) //設置線程分離屬性(deatch)
//thread 指定要分離的線程id
//分離指定的線程,被分離的線程退出時自動回收資源
//處於detach狀態的線程退出後自動回收資源,不會保存返回值,所以不需要被等待
線程安全
因爲線程是cpu調度的基本單位,因此多個線程可能會同時爭奪對臨界資源的操作,多個線程因爲臨界資源的爭搶寫入操作會導致程序邏輯的混亂/數據的二義性;同時,線程本身就是因爲通信方便以及成本低而廣爲使用,這樣就無法避免大量臨界資源的爭搶操作,這時候就必須要考慮如何保證線程安全。
線程間的同步與互斥
互斥: 任何時刻,互斥保證有且只有一個執行流進入臨界區,訪問臨界資源。
同步: 讓數據的訪問更加具有時序的可控性,保證對臨界資源的時序可控性。
互斥鎖 — 互斥
大部分情況,線程使用的數據都是局部變量,變量的地址空間在線程棧空間內,這種情況,變量歸屬單個線程,其他線程無法獲得這種變量。但有時候,很多變量都需要在線程間通過數據的共享,完成線程之間的交互,這樣的變量稱爲共享變量。而多個線程併發的操作共享變量,會帶來一些問題。因此,我們需要一扇門,來保證只有一個線程進入臨界區,Linux下,爲我們提供了一種方式,稱爲互斥鎖。
初始化互斥鎖變量
pthread_mutex_t mutex; //互斥鎖變量
//01、
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//02、
pthread_mutex_init(); //初始化
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
//mutex 互斥量
//attr 屬性
加鎖
pthread_mutex_lock //阻塞加鎖,獲取不到一直等待
int pthread_mutex_lock(pthread_mutex_t *mutex);
//成功返回0, 失敗返回錯誤信號
pthread_mutex_trylock(); //非阻塞加鎖,獲取不到直接退出
pthread_mutex_timedlock(); //限時阻塞加鎖
解鎖
pthread_mutex_unlock(); //解鎖
//在任何有可能退出的地方都必須解鎖
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//成功返回0, 失敗返回錯誤信號
釋放互斥鎖
pthread_mutex_destroy //釋放
int pthread_mutex_destroy(pthread_mutex_t *mutex);
條件變量 — 同步
滿足操作條件,纔可以操作,不滿足需要等待,而條件滿足就需要對其他線程修改條件,並通知一下等待的線程。
初始化條件變量
pthread_cond_t cond //條件變量
//01、
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
//02、
pthread_cond_init //初始化
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
//cond 條件變量
//attr 條件變量的屬性,通常置空
等待
pthread_cond_wait(); //等待
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex)
//cond 要在這個條件變量上等待
//mutex:互斥量
///這個函數需要在掛起之前對互斥鎖進行解鎖操作
//這個時候解鎖操作和掛起操作必須是一個原子操作,否則不安全
//條件變量中的解鎖和休眠操作必須是原子操作,並且在被喚醒後會將互斥鎖的值修改爲0
//條件變量的判斷必須是一個循環,否則如果有多個線程被同時喚醒,則對臨界資源的訪問不安全
pthread_cond_timedwait(); //限時等待
通知
pthread_cond_signal //通知(通知等待在條件變量上的線程/進程)
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
銷燬條件變量
pthread_cond_destroy(); //銷燬
int pthread_cond_destroy(pthread_cond_t *cond);
死鎖
一個程序一直獲取不到鎖,因此一直處於卡死狀態就稱爲死鎖。
死鎖產生的四個必要條件
- 互斥條件
- 只有一個能夠獲得鎖
- 不可剝奪條件
- 使用時別人不能釋放
- 請求與保持條件
- 保持第一個,請求第二個,如果拿不到,也不釋放第一個
- 環路等待條件
- A拿到第一個請求第二個,B拿到第二個請求第一個
預防死鎖
- 打破死鎖條件
- 加鎖順序一致
- 避免鎖爲釋放
- 資源一次性分配
避免死鎖算法
- 死鎖檢測
- 銀行家算法