天天看點

linux 庫全局變量_Linux基礎知識(八)

本篇介紹一些Linux線程相關内容.

1. 多線程管理

程序是具有獨立功能的程式在某個資料集合上的一次運作活動, 程序是系統進行資源配置設定和調用的獨立機關

線程是程序的一個實體, 它是競争配置設定 CPU 的基本機關

程序和線程的差別

(1) 線程是程序的一部分, 也被稱作輕量級的程序(2) 程序資源配置設定的基本機關, 線程是 CPU 排程的基本機關(3) 線程的建立比程序的建立系統開銷小
           

網絡伺服器程式通常需要處理多個用戶端的請求, 使用的并發的技術無非就是多程序和多線程

多程序技術存在很多的局限性, 比如: 要配置設定大量的資源, 程序 PID, 代碼段,資料段,堆區, 棧區..., 其系統消耗是非常大的, 會嚴重影響伺服器的處理速度, 是以在網絡程式設計過程中越來越多的使用線程技術, 實作并發處理

線程是一種輕量級的程序, 對系統資源的需求非常少, 線程隸屬于某個程序, 程序内部可以由很多線程, 線程共享程序的資源, 比如: 代碼段 , 資料段, 堆區, 環境變量表, 檔案描述符...

每個線程隻需要建立一個獨立的棧區就可以了(除棧區外的其他内容共享)每個程序内部都至少有一個線程, 叫做主線程( main() ), 主線程一旦結束, 程序随之結束, 程序結束該程序所擁有的所有線程每個線程的内部到碼都是順序執行的, 多線程之間的代碼亂序執行
           

線程并行的原理: 宏觀上并行, 微觀上串行

***** 程序是資源配置設定的機關, 如代碼區 資料段等***** 線程是競争配置設定 CPU 的基本機關
           

2. linux/unix 關于多線程的程式設計

POSIX (可移植作業系統接口) 标準 : 是IEEE為要在各種UNIX作業系統上運作軟體,而定義API的一系列互相關聯的标準的總稱

unix/linux 系統中提供的系統調用遵循了 POSIX 标準形式,是以系統中給應用工程師提供的系統函數的API ( 應用程式程式設計接口), 函數名, 參數是統一的, 隻要是在 unix/linux 系統, 都能通用

2.1 多線程的建立

linux 庫全局變量_Linux基礎知識(八)

編碼時使用頭檔案, 編譯連結時使用共享庫 pthread

int pthread_create ( pthread_t *thread, const pthread_attr_t *attr,  void *(*start_routine) (void *), void *arg);thread : 指向線程辨別符的指針, 傳回建立的新線程的 ID ID 是線程的唯一辨別, 本質就是一個正整數, 是一個傳出參數attr : 建立新線程時可以指定線程的某些屬性 一般給 0( NULL) 即可, 代表使用預設屬性start_routine : 函數指針, 指向了線程執行的主體, 線程運作函數的位址arg : 運作函數的參數, 傳給 start_routine 的參數傳回值 : 成功傳回0, 失敗傳回錯誤編碼
           

注意: 在多線程中盡量不要使用 errno, 因為擷取的 errno 有可能不是錯誤的原因

ps -Lf + PID : 檢視對應的程序中有幾個線程

2.2 多線程的運作

2.2.1 擷取線程的 ID

pthread_t pthread_self ( void );pthread_self() : 擷取線程的 ID, 傳回值就是線程的 ID成功傳回調用線程的 ID, 這個函數不會失敗
           

主線程退出, 會導緻程序退出, 緻使子線程得不到執行, 這種情況實際程式設計過程中要避免出現, 如果出現該情況, 它屬于程式設計邏輯問題, 編譯器檢出不出來

2.2.2 線程間的線程傳遞

(1) 全局變量

(2) ptread_create 函數的第四個參數

2.3 多線程的結束

正确的應該先是子線程結束, 然後後結束主線程

2.3.1 主線程如何監控到子線程的結束

pthread_join 函數

int pthread_join ( pthread_t thread, void **retval );作用 : 阻塞的方式等待thread指定的線程結束,當函數傳回時,被等待線程的資源被收回,如果線程已經結束,那麼該函數會立即傳回thread : 參數為被等待的線程辨別符retval : 使用者定義的指針,它可以用來存儲被等待線程的傳回值。傳回值 : 成功傳回0, 失敗傳回錯誤編碼
           

2.3.2 子線程結束時的傳回值問題

如何得到子線程的傳回值

(1) 全局變量, 把子線程的傳回值放入某一個全局變量, 主線程再去檢視

(2) 通過線程函數的傳回值

線程函數通過強轉得到的( void*)的傳回值會傳給 pthread_join 的第二個參數,當 pthread_join()函數第二個參數不為 0 的情況下
           

(3) 通過 pthread_create 函數的第四個參數

2.3.3 子線程是如何結束的

(1) 線程的主體函數中執行 return

(2) pthread_exit 函數

void pthread_exit (void*);如果線上程主體函數中執行return (void *)-1 等價于 pthread_exit ((void*)-1)
           

(3) 在程序的任何位置執行 exit 都會導緻整個程序的結束

2.4 線程資源的回收

線程的資源 : 棧空間 線程的傳回值

如何處理線程資源

(1) 置之不理

等到程序結束後自動回收, 該線程資源什麼時候被回收是不确定的

(2) pthread_join 函數

pthread_join 函數等待特定線程的結束( 不管 pthread_join 的第二個參數是否為 0),隻要該函數傳回, 結束線程的資源就會被立刻回收

(3) pthread_detach 函數

将某個線程設定為分離狀态, 設定為分離狀态的線程一旦結束, 占據的資源會被立刻回收

因為 pthread_join 函數要回收子線程資源的話, 就必須要等待子線程的結束, 那麼主線程就得等待, 如果此時主線程還有其他工作要完成, 這樣就會浪費資源

int pthread_detach ( pthread_t thread );thread : 要把哪個線程設定為分離狀态傳回值 : 成功傳回0, 失敗傳回錯誤編碼
           

(4) pthread_join / pthread_detach 使用

a) 一個新建立的線程最好要麼 pthread_join 要麼 pthread_detachb) 一個設定為分離狀态的線程, 再去對它調用 join 就沒有效果了c) 需要等待的線程使用 pthread_join, 不需要等帶的線程使用 pthread_detach
           

3. 線程的同步

3.1 線程同步的原因

多個線程共享資料段BSS 段堆區, 當定義一個全局變量 cnt, 該變量對于程序中所有線程都可見, 都可以修改 cnt, 子線程同時操作時會産生沖突, 導緻 cnt 的結果不确定

3.2. 互斥量

本質就是一把鎖( 可以參考檔案鎖)

程式設計步驟:

(1) 定義一個互斥量

pthread_mutex_t lock;
           

(2) 初始化互斥量

int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t * attr);mutex : 指向要初始化的互斥量attr : 互斥鎖屬性,NULL表示預設屬性傳回值 : 成功時傳回0,失敗時傳回錯誤碼
           

(3) 加鎖臨界區, 保證隻有一個線程可以使用共享資源

int pthread_mutex_lock(pthread_mutex_t *mutex);mutex : 指向要加鎖的互斥鎖對象 如果加鎖成功, 立即傳回 如果 lock 已經處于所得狀态, 該函數阻塞等待, 直到 lock 處于unlock 狀态, 加鎖成功并傳回傳回值 : 成功時傳回0,失敗時傳回錯誤碼
           

pthread_mutex_trylock()函數加鎖不成功立刻傳回

(4) 操作共享資源

對共享資源進行操作,此時隻有目前線程在操作,是以不會發生沖突

(5) 解鎖/釋放鎖

int pthread_mutex_unlock(pthread_mutex_t *mutex);mutex : 指向要解鎖的互斥鎖對象傳回值 : 成功時傳回0,失敗時傳回錯誤碼
           

(6) 回收互斥量/互斥鎖

int pthread_mutex_destroy(pthread_mutex_t *mutex);mutex : 指向要銷毀的互斥鎖對象傳回值 : 成功時傳回0,失敗時傳回錯誤碼
           

死鎖産生

1) 一個線程試圖對一個互斥量加兩次鎖2) 線程一擷取了 A 鎖 再試圖擷取 B 鎖3) 線程二擷取了 B 鎖 再試圖擷取 A 鎖, 互相之間有沖突
           

避免死鎖

1) 加鎖時按順序加鎖2) 使用pthread_mutex_trylock()函數 線程一 trylock 擷取 A 鎖, 再試圖 trylock B 鎖, 如果不成功釋放 A 鎖
           

3.3 信号量

信号量集用于程序之間的同步操作

信号量是用于線程之間的同步操作

信号量本質上就是一個計數器(int), 可以用于控制通路共享資源的最大線程數,當信号量的值為 1 時, 等同于互斥量

信号量的程式設計步驟:

(1) 定義一個信号量

sem_t sem;

(2) 初始化信号量

int sem_init(sem_t *sem, int pshared, unsigned int value);sem : 需要初始化的信号量 sempshared : 控制信号量的類型, =0, 代表該信号量用于多線程間的同步 >0, 表示可以共享,用于多個相關程序間的同步value : 要初始化的目标值, 也就是要把信号量 sem 初始化為多少傳回值 : 成功傳回0, 失敗傳回-1, 錯誤資訊存于errno
           

(3) 擷取信号量

拿到信号量就可以操作共享資源, 拿不到信号量就不能操作共享資源

本質上就是給 計數-1 信号量-1int sem_wait(sem_t *sem); /*信号量-1, 不夠減的話則阻塞等待*/int sem_trywait(sem_t *sem); /*信号量-1, 不夠減傳回出錯資訊*/int sem_timedwait(sem_t *sem, timeout); /*信号量-1, 不夠減阻塞等待, 直到夠減或者等待時間逾時*/
           

(4) 操作共享資源

(5) 釋放信号量

本質上就是給 計數+1 信号量+1int sem_post(sem_t *sem); /*指定的信号量 sem 的值加 1*/
           

(6) 回收信号量

int sem_destroy(sem_t *sem); /*對用完的信号量的清理*/
           

3.4 條件變量

生産者消費者模型

生産者向循環緩沖區放入産品,如果緩沖區滿阻塞,當緩沖區不滿的時繼續向其中放入産品消費者從循環緩沖區取出産品,如果緩沖區空阻塞,當緩沖區不空的時繼續從其中取出産品循環緩沖區是共享資源, 需要使用互斥量加鎖通路
           

條件變量是用來等待線程而不是上鎖的,條件變量通常和互斥鎖一起使用

條件變量之是以要和互斥鎖一起使用,主要是因為互斥鎖的一個明顯的特點就是它隻有兩種狀态:鎖定和非鎖定,而條件變量可以通過允許線程阻塞和等待另一個線程發送信号來彌補互斥鎖的不足,是以互斥鎖和條件變量通常一起使用
           

程式設計步驟

(1) 定義條件變量

pthread_cond_t cond;

(2) 初始化條件變量

int pthread_cond_init(pthread_cond_t *cv, const pthread_condattr_t *cattr);cv : 要初始化的條件變量cattr : 條件變量的屬性, 通常給 0 就可以了
           

(3) 暫停線程, 使調用線程睡入條件變量 cond 指向的隊列, 同時釋放互斥鎖

int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex);cv : 把線程放入條件變量 對應的隊列mutex : 要釋放那個的互斥鎖
           

(4) 喚醒線程/解除暫停, 把線程從條件變量指定的隊列中取出

int pthread_cond_signal(pthread_cond_t *cv);cv : 喚醒響應的線程
           

(5) 銷毀條件變量

int pthread_cond_destroy(pthread_cond_t *cv);cv : 要銷毀的條件變量