1. 程式人生 > 其它 >20191302 第四章學習筆記

20191302 第四章學習筆記

併發程式設計

摘要

  • 本章論述了併發程式設計,介紹了平行計算的概念,指出了平行計算的重要性;
  • 比較了順序演算法與並行演算法,以及並行性與併發性;
  • 解釋了執行緒的原理及其相對於程序的優勢;
  • 通過示例介紹了 Pthread 中的執行緒操作,包括執行緒管理函式,互斥量、連線、條件變數和屏障等執行緒同步工具;
  • 通過具體示例演示瞭如何使用執行緒進行併發程式設計,包括矩陣計算、快速排序和用併發執行緒求解線性方程組等方法;
  • 解釋了死鎖問題,並說明了如何防止併發程式中的死鎖問題;
  • 討論了訊號量,並論證了它們相對於條件變數的優點。

並行性和併發性

  • 真正的並行執行只能在多個處理元件的系統中實現,比如多處理器或多核系統。
  • 在單CPU系統中,併發性是通過多工處理實現的。

執行緒

  • 執行緒的原理
    • 在核心模式下,各程序在唯一地址空間上執行,與其他程序是分開的;
    • 每個程序都是一個獨立單元,只有一個執行路徑;
    • 執行緒是某程序同一地址空間上的獨立執行單元,如果只有一個主執行緒,那麼程序和執行緒並沒有什麼本質區別。
  • 執行緒的優點
    1. 執行緒建立和切換速度更快。
    2. 執行緒的響應速度更快。
    3. 執行緒更適合平行計算
  • 執行緒的缺點
    1. 由於地址空間共享,執行緒需要來自使用者的明確同步。
    2. 許多庫函式可能對執行緒不安全。
    3. 在單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最複雜,其使用步驟為
        1. 定義一個pthread屬性變址pt:hread_attr_tattr
        2. pthread_attr_init(&attr)初始化屈性變掀。
        3. 設定屬性變垃並在pthread_create()呼叫中使用。
        4. 必要時,通過pthread_attr_destroy(&attr)釋放attr資源。
    • 執行緒終止
      • 執行緒函式結束後,執行緒即終止,或者,執行緒可以呼叫函式int pthraad_exit {void *status)進行顯式終止,其中狀態是執行緒的退出狀態。
    • 執行緒連線
      • 一個執行緒可以等待另一個執行緒的終止, 通過:
        int pthread_join (pthread_t thread, void **status__ptr);
      • 終止執行緒的退出狀態以status_ptr返回。

執行緒同步

  • 當多個執行緒試圖修改同一共享變數或資料結構時,如果修改結果取決於執行緒的執行順序,則稱之為競態條件。

  • 互斥量

    • 在 Pthread中,鎖被稱為互斥量,意思是相互排斥。
    • 互斥變呈是用 ptbread_mutex_t型別宣告的在使,用之前必須對它們進行初始化。
      • 有兩種方法可以初始化互斥址:
        1. 靜態方法:pthreaa—mutex_t m = PTHREAD_MUTEX_INITIALIZER,定義互斥量 m, 並使用預設屬性對其進行初始化。
        2. 動態方法,使用 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上同時執行。
    • 並行是指同時發生的兩個併發事件,具有併發的含義,而併發則不一定並行,也亦是說併發事件之間不一定要同一時刻發生。

實踐

#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 () ;
}
  • 執行結果