Linux — 多線程

線程概念

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拿到第二個請求第一個

預防死鎖

  • 打破死鎖條件
  • 加鎖順序一致
  • 避免鎖爲釋放
  • 資源一次性分配

避免死鎖算法

  • 死鎖檢測
  • 銀行家算法
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章