20191302 第四章學習筆記
阿新 • • 發佈:2021-10-31
併發程式設計
摘要
- 本章論述了併發程式設計,介紹了平行計算的概念,指出了平行計算的重要性;
- 比較了順序演算法與並行演算法,以及並行性與併發性;
- 解釋了執行緒的原理及其相對於程序的優勢;
- 通過示例介紹了 Pthread 中的執行緒操作,包括執行緒管理函式,互斥量、連線、條件變數和屏障等執行緒同步工具;
- 通過具體示例演示瞭如何使用執行緒進行併發程式設計,包括矩陣計算、快速排序和用併發執行緒求解線性方程組等方法;
- 解釋了死鎖問題,並說明了如何防止併發程式中的死鎖問題;
- 討論了訊號量,並論證了它們相對於條件變數的優點。
並行性和併發性
- 真正的並行執行只能在多個處理元件的系統中實現,比如多處理器或多核系統。
- 在單CPU系統中,併發性是通過多工處理實現的。
執行緒
- 執行緒的原理
- 在核心模式下,各程序在唯一地址空間上執行,與其他程序是分開的;
- 每個程序都是一個獨立單元,只有一個執行路徑;
- 執行緒是某程序同一地址空間上的獨立執行單元,如果只有一個主執行緒,那麼程序和執行緒並沒有什麼本質區別。
- 執行緒的優點
- 執行緒建立和切換速度更快。
- 執行緒的響應速度更快。
- 執行緒更適合平行計算
- 執行緒的缺點
- 由於地址空間共享,執行緒需要來自使用者的明確同步。
- 許多庫函式可能對執行緒不安全。
- 在單CPU系統上,使用執行緒解決間題實際上要比使用順序程式慢,這是由在執行時建立執行緒和切換上下文的系統開銷造成的。
- 執行緒操作
- 執行緒可在核心模式或使用者模式下執行。
- 在使用者模式下,執行緒在程序的相同地址空間中執行,但每個執行緒都有自己的執行堆疊。
- 執行緒是獨立的執行單元,可根據作業系統核心的排程策略,對核心進行系統呼叫,變為桂起啟用以繼續執行等。
- 為了利用執行緒的共享地址空間,作業系統核心的排程策略可能會優先選擇同一程序中的執行緒,而不是不同程序中的執行緒。
- 執行緒管理函式
- Pthread庫提供了用於執行緒管理的以下API
pthread_create(thread, attr, function, arg): create thread pthread_exit(status):terminate thread pthread_cancel(thread) : cancel thread pthread_attr_init(attr) : initialize thread attributes pthread_attr_destroy(attr): destroy thread attribute
- 建立執行緒
- 使用pthread_create()函式建立執行緒。
int pthread_create (pthread_t *pthread_id,pthread_attr_t *attr,void * (*func) (void *), void *arg);
- 其中,attr最複雜,其使用步驟為
- 定義一個pthread屬性變址
pt:hread_attr_tattr
。 - 用
pthread_attr_init(&attr)
初始化屈性變掀。 - 設定屬性變垃並在
pthread_create()
呼叫中使用。 - 必要時,通過
pthread_attr_destroy(&attr)
釋放attr資源。
- 定義一個pthread屬性變址
- 使用pthread_create()函式建立執行緒。
- 執行緒終止
- 執行緒函式結束後,執行緒即終止,或者,執行緒可以呼叫函式
int pthraad_exit {void *status)
進行顯式終止,其中狀態是執行緒的退出狀態。
- 執行緒函式結束後,執行緒即終止,或者,執行緒可以呼叫函式
- 執行緒連線
- 一個執行緒可以等待另一個執行緒的終止, 通過:
int pthread_join (pthread_t thread, void **status__ptr);
- 終止執行緒的退出狀態以status_ptr返回。
- 一個執行緒可以等待另一個執行緒的終止, 通過:
- Pthread庫提供了用於執行緒管理的以下API
執行緒同步
-
當多個執行緒試圖修改同一共享變數或資料結構時,如果修改結果取決於執行緒的執行順序,則稱之為競態條件。
-
互斥量
- 在 Pthread中,鎖被稱為互斥量,意思是相互排斥。
- 互斥變呈是用
ptbread_mutex_t
型別宣告的在使,用之前必須對它們進行初始化。- 有兩種方法可以初始化互斥址:
- 靜態方法:
pthreaa—mutex_t m = PTHREAD_MUTEX_INITIALIZER
,定義互斥量 m, 並使用預設屬性對其進行初始化。 - 動態方法,使用 pthread_ mutex _init() 函式
- 靜態方法:
- 有兩種方法可以初始化互斥址:
- 執行緒通過互斥量來保護共享資料物件
-
死鎖預防
- 死鎖是一種狀態,在這種狀態下,許多執行實體相互等待,因此都無法繼續下去。
- 死鎖預防,試圖在設計並行演算法時防止死鎖的發生。
- 一種簡單的死鎖預防方法是對互斥量進行排序,並確保每個執行緒只在一個方向請求互斥量,這樣請求序列中就不會有迴圈。
-
條件變數
- 條件變數提供了一種執行緒協作的方法。
- 在Pthread中,使用型別
pthread_cond_t
來宣告條件變數,而且必須在使用前進行初始化。 - 與互斥變數一樣,條件變數也可以通過兩種方法進行初始化。
- 靜態方法:
pthread_cond_t con= PTHREAD_COND_INITIALIZER
;定義一個條件變屾con,並使用預設屬性對其進行初始化。 - 動態方法:使用
pthread_cond_init()
函式,可通過attr引數設定條件變數。為簡便起見,我們總是使用NULLattr引數作為預設屬性。
- 靜態方法:
-
訊號量
- 訊號量是程序同步的一般機制。
- 訊號量是一種資料結構
struct sem{ int value; struct process *queue }s;
問題與解決
- 並行性和併發性的區別與聯絡
- 併發的實質是一個物理CPU(也可以多個物理CPU) 在若干道程式之間多路複用;
- 所有的併發處理都有排隊等候,喚醒,執行至少三個這樣的步驟;
- 併發性,又稱共行性,是指能處理多個同時性活動的能力;
- 併發性是對有限物理資源強制行使多使用者共享以提高效率。
- 並行性指兩個或兩個以上事件或活動在同一時刻發生。在多道程式環境下,並行性使多個程式同一時刻可在不同CPU上同時執行。
- 並行是指同時發生的兩個併發事件,具有併發的含義,而併發則不一定並行,也亦是說併發事件之間不一定要同一時刻發生。
- 併發的實質是一個物理CPU(也可以多個物理CPU) 在若干道程式之間多路複用;
實踐
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
typedef struct{
int upperbound;
int lowerbound;
}PARM;
#define N 10
int a[N]={5,1,6,4,7,2,9,8,0,3};// unsorted data
int print(){//print current a[] contents
int i;
printf("[");
for(i=0;i<N;i++)
printf("%d ",a[i]);
printf("]\n");
}
void *Qsort(void *aptr){
PARM *ap, aleft, aright;
int pivot, pivotIndex,left, right,temp;
int upperbound,lowerbound;
pthread_t me,leftThread,rightThread;
me = pthread_self();
ap =(PARM *)aptr;
upperbound = ap->upperbound;
lowerbound = ap->lowerbound;
pivot = a[upperbound];//pick low pivot value
left = lowerbound - 1;//scan index from left side
right = upperbound;//scan index from right side
if(lowerbound >= upperbound)
pthread_exit (NULL);
while(left < right){//partition loop
do{left++;} while (a[left] < pivot);
do{right--;}while(a[right]>pivot);
if (left < right ) {
temp = a[left];a[left]=a[right];a[right] = temp;
}
}
print();
pivotIndex = left;//put pivot back
temp = a[pivotIndex] ;
a[pivotIndex] = pivot;
a[upperbound] = temp;
//start the "recursive threads"
aleft.upperbound = pivotIndex - 1;
aleft.lowerbound = lowerbound;
aright.upperbound = upperbound;
aright.lowerbound = pivotIndex + 1;
printf("%lu: create left and right threadsln", me) ;
pthread_create(&leftThread,NULL,Qsort,(void * )&aleft);
pthread_create(&rightThread,NULL,Qsort,(void *)&aright);
//wait for left and right threads to finish
pthread_join(leftThread,NULL);
pthread_join(rightThread, NULL);
printf("%lu: joined with left & right threads\n",me);
}
int main(int argc, char *argv[]){
PARM arg;
int i, *array;
pthread_t me,thread;
me = pthread_self( );
printf("main %lu: unsorted array = ", me);
print( ) ;
arg.upperbound = N-1;
arg. lowerbound = 0 ;
printf("main %lu create a thread to do QS\n" , me);
pthread_create(&thread,NULL,Qsort,(void * ) &arg);//wait for Qs thread to finish
pthread_join(thread,NULL);
printf ("main %lu sorted array = ", me);
print () ;
}
- 執行結果