2017-2018-1 20155320 《信息安全系統設計基礎》第十三週學習總結

2017-2018-1 20155320 《信息安全系統設計基礎》第十三週學習總結

參考老師提供的教材內容導讀html

本週的內容是要學習以爲最重要的一章,我最終決定選擇第12章——併發編程git

由於對於併發這個概念在計算機學習中在不少方面都要用到。編程

貫徹十二章有幾個概念:安全

  1. 進程
  2. 線程
  3. 併發
  4. 上下文調度 /切換服務器

    在操做系統中,CPU切換到另外一個進程須要保存當前進程的狀態並恢復另外一個進程的狀態:當前運行任務轉爲就緒(或者掛起、刪除)狀態,另外一個被選定的就緒任務成爲當前任務。上下文切換包括保存當前任務的運行環境,恢復將要運行任務的運行環境。多線程

本次再度學習十二章一是對之前學過的內容查漏補缺二是鞏固重點知識(尤爲是線程的相關知識),所以將不會徹底再描述每個知識點但將結合實例進行學習併發

教材學習內容總結

  • 若是邏輯控制流在時間上重疊,那麼他們就是併發的。
  • 現代操做系統提供了三種基本的構造併發程序的辦法
    • 進程:每一個邏輯控制流都是一個進程
    • I/O多路複用:在這種形式的併發編程中,應用程序在一個進程的上下文中顯示地調度他們的邏輯流
    • 線程:線程是運行在一個單一進程上下文中的邏輯流。

如下就是這三種構造方法的學習函數

12.1基於進程的併發編程

  • 是構造併發程序最簡單的方法
  • 父子進程的已鏈接描述符都指向同一個文件表項,父進程關閉它的鏈接描述符是相當重要的。
  • 父進程派生子進程爲客戶端提供服務,而父進程本身用來等待下一個鏈接請求。
    post

  • 父進程派生一個子進程來處理每一個新的鏈接請求
  • 關於進程的echo服務器學習

    使用SIGCHLD處理程序來回收僵死子進程的資源。

    父進程必須關閉他們各自的connfd拷貝(已鏈接的描述符),避免存儲器泄露。

    由於套接字的文件表表項中的引用計數,直到父子進程的connfd都關閉了,到客戶端的鏈接纔會終止。

  • 注意

    1.父進程須要關閉它的已鏈接描述符的拷貝(子進程也須要關閉)

    2.必需要包括一個SIGCHLD處理程序來回收僵死子進程的資源

    3.父子進程之間共享文件表,可是不共享用戶地址空間。

  • 進程的優劣

父、子進程共享狀態信息:共享文件表但不共享用戶空間。

優勢:防止虛擬存儲器被錯誤覆蓋

缺點:開銷高,共享狀態信息才須要IPC機制

12.2 基於I/O多路複用的併發編程

  • 基本思想:就是使用select函數,要求內核掛起進程,只有在一個或多個I/O事件發生後,纔將控制返回給應用程序。

  • Select函數

int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval*timeout);

1.返回值

返回已準備好的描述符的非0個數,若出錯則爲-1

2.參數詳解

int maxfdp是一個整數值,是指集合中全部文件描述符的範圍,即全部文件描述符的最大值加1,不能錯!

fd_set * readfds是指向fd_set結構的指針,這個集合中應該包括文件描述符

fd_set * writefds是指向fd_set結構的指針,這個集合中應該包括文件描述符,咱們是要監視這些文件描述符的寫變化的,即咱們關心是否能夠向這些文件中寫入數據了,若是這個集合中有一個文件可寫,select就會返回一個大於0的值,表示有文件可寫,若是沒有可寫的文件,則根據timeout參數再判斷是否超時,若超出timeout的時間,select返回0,若發生錯誤返回負值。能夠傳入NULL值,表示不關心任何文件的寫變化。

fd_set * errorfds同上面兩個參數的意圖,用來監視文件錯誤異常。

struct timeval * timeout是select的超時時間它可使select處於三種狀態:

第一,若將NULL以形參傳入,即不傳入時間結構,就是將select置於阻塞狀態,必定等到監視文件描述符集合中某個文件描述符發生變化爲止;

第二,若將時間值設爲0秒0毫秒,就變成一個純粹的非阻塞函數,無論文件描述符是否有變化,都馬上返回繼續執行,文件無變化返回0,有變化返回一個正值;

第三,timeout的值大於0,這就是等待的超時時間,即select在timeout時間內阻塞,超時時間以內有事件到來就返回了,不然在超時後無論怎樣必定返回,返回值同上述。
  • 經過實例來學習Select函數
#include <sys/types.h>   
#include <sys/time.h>   
#include <stdio.h>   
#include <fcntl.h>   
#include <sys/ioctl.h>   
#include <unistd.h>   
  
int main()   
{   
    char buffer[128];   
    int result, nread;   
    fd_set inputs, testfds;   
    struct timeval timeout;   
    FD_ZERO(&inputs);//用select函數以前先把集合清零    
     FD_SET(0,&inputs);//把要檢測的句柄——標準輸入(0),加入到集合裏。  
     while(1)   
    {   
       testfds = inputs;   
       timeout.tv_sec = 2;   
       timeout.tv_usec = 500000;   
       result = select(FD_SETSIZE, &testfds, (fd_set *)0, (fd_set *)0, &timeout);   
       switch(result)   
       {   
       case 0:   
           printf("timeout/n");   
           break;  
       case -1:   
           perror("select");   
           exit(1);   
       default:   
           if(FD_ISSET(0,&testfds))   
           {   
               ioctl(0,FIONREAD,&nread);//取得從鍵盤輸入字符的個數,包括回車。   
               if(nread == 0)   
               {   
                  printf("keyboard done/n");   
                  exit(0);   
               }   
               nread = read(0,buffer,nread);   
               buffer[nread] = 0;   
               printf("read %d from keyboard: %s", nread, buffer);   
         }   
         break;   
      }   
   }   
   return 0;  
}
  • 上面這個簡單的實例就是用來讀取鍵盤輸入值,超時間隔2.5秒,輸出用戶輸入的字符個數。

  • select函數學習總結:
    對比而言,select有其優點,Select能夠實現非阻塞,即進程或線程執行此函數時沒必要非要等待事件的發生,一旦執行確定返回,以返回值的不一樣來反映函數的執行狀況,若是事件發生則與阻塞方式相同,若事件沒有發生則返回一個代碼來告知事件未發生,而進程或線程繼續執行,因此效率較高。

12.3 基於線程的併發編程

這一種方法是基於前兩中的複合

  • 線程:以前我對線程和進程老是分不清,線程就是運行在上下文的邏輯流,每一個線程都有其惟一的整數ID(內核以此區分線程)。
  • 每一個線程開始都是由單一線程開始的,稱爲主線程;在某一個時刻主線程建立一個對等線程,開始進場併發的運行,他們之間經過上下文切換

  • 線程執行與進程執行的區別
    • 一個線程的上下文比一個進程的上下文小得多,切換也要快得多
    • 一個線程能殺死他的任何對等線程或者等待他的任意對等線程終止

建立線程

  • 線程經過調用pthread_create來建立其餘線程。
int pthread_create(pthread_t *tid,pthread_attr_t *attr,func *f,void *arg);
成功則返回0,出錯則爲非零
  • 函數參數:

    • tidp:線程標識符指針
    • attr:線程屬性指針(不要求則傳空)
    • start_rtn:線程運行函數的起始地址,(須要用戶本身定義)
    • arg:傳遞給線程運行函數的參數

函數返回時,參數tid包含新建立的線程的ID,新線程能夠經過調用pthread_self函數來得到本身的線程ID。

pthread_t pthread_self(void);

返回調用者的線程ID。

  • 建立線程實踐:
#include <pthread.h>

pthread_t ntid;

void printids(const char *s)
{
    pid_t   pid;
    pthread_t   tid;
    
    pid = getpid();
    tid = pthread_self();

    printf("%s pid %lu tid %lu (0x%lx)\n", s, (unsigned long)pid, (unsigned long)tid, (unsigned long)tid);
}

void* thr_fn(void *arg)
{
    printids("new thread:");
    return((void*)0);
}

int main()
{
    int     err;
    
    err = pthread_create(&ntid, NULL, thr_fn, NULL);
    if(err!=0)
    {
        printf("can't create thread\n");
        exit(1);
    }
    printids("main thread:");
    sleep(2);
    exit(0);
}
  • 此實例既使用了pthread_create()建立也使用了pthread_self()獲取ID,打印出主線程與建立 的對等線程的ID

PS:有關線程相關代碼編譯時要加上 -lpthread,是由於鏈接時須要使用庫libpthread.a不然出錯

  • 問題1:線程和對等線程們是如何存儲的以達到安全地共享數據的效果。
  • 解決1

終止線程和回收線程

  • 一個線程是經過如下方式之一來終止的。

    • 當頂層的線程例程返回時,線程會隱式地終止。

    • 經過調用pthread_exit函數,線程會顯式地終止。他會等待其餘對等線程終止而後再終止主線程

void pthread_exit(void *thread_return);
  • 問題2:使用函數pthread_exit退出線程,這是線程的主動行爲;因爲一個進程中的多個線程是共享數據段的,所以一般在線程退出以後,退出線程所佔用的資源並不會隨着線程的終止而獲得釋放,那被佔用的資源怎麼釋放呢?
  • 解決:能夠用pthread_join()函數來同步並釋放資源。
int pthread_join(pthread_t tid,void **thread_return);
成功則返回0,出錯則爲非零
  • 實踐實例
#include <stdio.h>  
#include <pthread.h>  
  
/*線程一*/  
void thread_1(void)  
{  
    int i=0;  
    for(i=0;i<=6;i++)  
    {  
        printf("This is a pthread_1.\n");  
        if(i==2)  
            pthread_exit(0);                      //用pthread_exit()來調用線程的返回值,用來退出線程,可是退出線程所佔用的資源不會隨着線程的終止而獲得釋放  
        sleep(1);  
    }  
}  
  
/*線程二*/  
void thread_2(void)  
{  
    int i;  
    for(i=0;i<3;i++)  
        printf("This is a pthread_2.\n");           
    pthread_exit(0);                              //用pthread_exit()來調用線程的返回值,用來退出線程,可是退出線程所佔用的資源不會隨着線程的終止而獲得釋放  
}  
  
int main(void)  
{  
    pthread_t id_1,id_2;  
    int i,ret;  
/*建立線程一*/  
    ret=pthread_create(&id_1,NULL,(void  *) thread_1,NULL);  
    if(ret!=0)  
    {  
        printf("Create pthread error!\n");  
    return -1;  
    }  
/*建立線程二*/  
     ret=pthread_create(&id_2,NULL,(void  *) thread_2,NULL);  
    if(ret!=0)  
    {  
        printf("Create pthread error!\n");  
    return -1;  
    }  
/*等待線程結束*/  
    pthread_join(id_1,NULL);  
    pthread_join(id_2,NULL);  
    return 0;  
}
  • pthread_exit()調用線程的返回值,可由其餘函數如pthread_join來檢索獲取

分離線程

  • 在任何一個時間點上,線程是可結合或可分離的。一個可結合的線程可以被其餘線程收回其資源和殺死,在被回收以前,它的存儲器資源是沒有被釋放的。分離的線程則相反,資源在其終止時自動釋放。
int pthread_deacth(pthread_t tid);
成功則返回0,出錯則爲非零
  • 實例
#include <pthread.h>
 #include <stdio.h>
            void *doit(void *arg){
                printf("arg...\n");
                sleep(1);
                return  NULL;
            }
            int main(void){
                pthread_t tid;
                pthread_create(&tid,NULL,doit,NULL);
                pthread_detach(tid);
            sleep(1);
                return 0;
            }

初始化線程

  • pthread_once容許初始化與線程例程相關的狀態。
pthread_once_t once_control=PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t *once_contro,

void (*init_routine)(void));
老是返回0

用信號量實現同步/互斥

  • 轉換規則:

    • 合法的轉換是向右或者向上,即某一個線程中的一條指令完成
    • 兩條指令不能在同一時刻完成,即不容許出現對角線
    • 程序不能反向運行,即不能出現向下或向左
  • 信號量與操做系統中的沒有區別

    1.P(s):若是s是非零的,那麼P將s減一,而且當即返回。若是s爲零,那麼就掛起這個線程,直到s變爲非零。

    2.V(s):將s加一,若是有任何線程阻塞在P操做等待s變爲非零,那麼V操做會重啓線程中的一個,而後該線程將s減一,完成P操做。

int sem_init(sem_t *sem,0,unsigned int value);//將信號量初始化爲value
int sem_wait(sem_t *s);//P(s)
int sem_post(sem_t *s);//V(s)
  • 用信號量實現同步實例
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>

void *thread_function(void *arg);
sem_t bin_sem;

#define WORK_SIZE 1024
char work_area[WORK_SIZE];      /* 用來存放輸入內容 */

int main() {
  int res;                    /* 暫存一些命令的返回結果 */
  pthread_t a_thread;         /* 織帶新建的線程 */
  void *thread_result;       /* 存放線程處理結果 */

  res = sem_init(&bin_sem, 0, 0);   /* 初始化信號量,而且設置初始值爲0*/
  if (res != 0) {
    perror("Semaphore initialization failed");
    exit(EXIT_FAILURE);
  }
  res = pthread_create(&a_thread, NULL, thread_function, NULL);   /* 建立新線程 */
  if (res != 0) {
    perror("Thread creation failed");
    exit(EXIT_FAILURE);
  }
  printf("Inout some text, Enter 'end' to finish\n");
  while(strncmp("end", work_area, 3) != 0) {             /* 當工做區內不是以end開頭的字符串時...*/
    fgets(work_area, WORK_SIZE, stdin);                  /* 從標準輸入獲取輸入到worl_area */
    sem_post(&bin_sem);                                  /* 信號量+1 */
  }
  printf("\nWaiting for thread to finish...\n");
  res = pthread_join(a_thread, &thread_result);         /* 等待線程結束 */
  if (res != 0) {
    perror("Thread join failed");
    exit(EXIT_FAILURE);
  }
  printf("Thread joined\n");
  sem_destroy(&bin_sem);                               /* 銷燬信號量 */
  exit(EXIT_SUCCESS);
}

void *thread_function(void *arg) {
  sem_wait(&bin_sem);                                 /* 等待信號量有大於0的值而後-1 */
  while(strncmp("end", work_area, 3) != 0) {
    printf("You input %ld characters\n", strlen(work_area)-1);   /* 獲取輸入字符串長度 8*/
    sem_wait(&bin_sem);                               /* 等待信號量有大於0的值而後-1 */
  }
  pthread_exit(NULL);
}
  • 獲取輸入的字符串長度,wait信號量有大於0的值而後-1

  • 用信號量實現互斥在調度共享資源的時候尤爲重要

    • 二元信號量(互斥鎖):將每一個共享變量與一個信號量s聯繫起來,而後用P(s)(加鎖)和V(s)(解鎖)操做將相應的臨界區包圍起來。

    • 禁止區:s<0,由於信號量的不變性,沒有實際可行的軌跡線可以直接接觸不安全區的部分

  • 互斥實例

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>

void *thread_function(void *arg);
pthread_mutex_t work_mutex;

#define WORK_SIZE 1024
char work_area[WORK_SIZE];
int time_to_exit = 0;                           /* 用來控制是否退出*/

int main() {
  int res;
  pthread_t a_thread;
  void *thread_result;

  res = pthread_mutex_init(&work_mutex,NULL);    /* 初始化一個互斥鎖 */
  if (res != 0) {
    perror("Mutex initialization failed");
    exit(EXIT_FAILURE);
  }
  res = pthread_create(&a_thread, NULL, thread_function, NULL);  /* 建立一個新線程 */
  if (res != 0) {
    perror("Thread creation failed");
    exit(EXIT_FAILURE);
  }
  pthread_mutex_lock(&work_mutex);                       /* 嘗試對互斥量加鎖 */
  printf("Input some text, Enter 'end' to finish\n");
  while(!time_to_exit) {                                   /* 檢查是否是該退出*/
    fgets(work_area, WORK_SIZE, stdin);                   /* 從標準輸入獲取輸入到work_area */
    pthread_mutex_unlock(&work_mutex);                   /* 解鎖互斥量 */
    while(1) {
      pthread_mutex_lock(&work_mutex);
      if (work_area[0] != '\0') {                      /* 持續檢查work_area 是否爲空, 若是不爲空繼續等待,若是爲空,則從新讀取輸入到work_area*/
        pthread_mutex_unlock(&work_mutex);
        sleep(1);
      }
      else {
        break;
      }
    }
  }
  pthread_mutex_unlock(&work_mutex);
  printf("\nWaiting for thread to finish...\n");
  res = pthread_join(a_thread, &thread_result);
  if (res != 0) {
    perror("Thread join failed");
    exit(EXIT_FAILURE);
  }
  printf("Thread joined\n");
  pthread_mutex_destroy(&work_mutex);
  exit(EXIT_SUCCESS);
}

void *thread_function(void *arg) {
  sleep(1);
  pthread_mutex_lock(&work_mutex);                     /* 嘗試加鎖互斥量 */
  while(strncmp("end", work_area, 3) != 0) {           /* 當work_area裏的值不是以end開頭時*/
    printf("You input %ld characters\n", strlen(work_area) -1);     /* 輸出輸入的字符長度 */
    work_area[0] = '\0';                                      /* work_area設置爲空 */
    pthread_mutex_unlock(&work_mutex);
    sleep(1);
    pthread_mutex_lock(&work_mutex);
    while (work_area[0] == '\0') {              /* 持續檢查work_area 直到它裏面有輸入值*/
      pthread_mutex_unlock(&work_mutex);
      sleep(1);
      pthread_mutex_lock(&work_mutex);
    }
  }
  time_to_exit = 1;                        /* 當輸入end後,設置退出標誌 */
  work_area[0] = '\0';
  pthread_mutex_unlock(&work_mutex);
  pthread_exit(0);
}
  • 實現結果與同步沒有區別,可是重點是互斥鎖的加和解

詳細學習了一下互斥鎖

參考互斥鎖
互斥鎖的基本流程爲:

初始化一個互斥鎖:pthread_mutex_init()函數

加鎖:pthread_mutex_lock()函數或者pthread_mutex_trylock()函數

對共享資源的操做

解鎖:pthread_mutex_unlock()函數

註銷互斥鎖:pthread_mutex_destory()函數
  • 加鎖須要注意:
    pthread_mutex_lock()函數和pthread_mutex_trylock()函數的過程略有不一樣:
  1. 當使用pthread_mutex_lock()函數進行加鎖時,若此時已經被鎖,則嘗試加鎖的線程會被阻塞,直到互斥鎖被其餘線程釋放,當pthread_mutex_lock()函數有返回值時,說明加鎖成功;

  2. 而使用pthread_mutex_trylock()函數進行加鎖時,若此時已經被鎖,則會返回EBUSY的錯誤碼。
  • 解鎖的兩個條件:

    解鎖前,互斥鎖必須處於鎖定狀態;

    必須由加鎖的線程進行解鎖。

感想:我的感受互斥鎖與操做系統中提到的門很像,加鎖就是關門,當達到加鎖條件時關門,只有等門裏邊的執行完,才能解鎖。雖然互斥鎖叫互斥鎖,可是他是用來解決同步問題的,使得執行起來更有條理,不會亂。

如下爲幾個重要問題的學習

生產者-消費者問題

新感悟:再參考了一下網上的資料進行了學習,相比當時在操做系統上的理論學習和書本上關於同步互斥的講解又有了新的理解,不少網上的資料都說要先同步再互斥,但我感受這樣並非必須的,若是生產者中先同步再互斥,而消費者先互斥再同 步,或反之,都不會出現死鎖,由於它們間並無交叉關係。可是先同步,再互斥能夠獲得更 好的併發性。

PS:如下兩句順序不要變

sem_wait(&room_sem);  
pthread_mutex_lock(&mutex);
#include 
#include 
#include 
#include 
#include
#define N 2   // 消費者或者生產者的數目
#define M 10 // 緩衝數目
int in = 0;   // 生產者放置產品的位置
int out = 0; // 消費者取產品的位置
int buff[M] = {0}; // 緩衝初始化爲0, 開始時沒有產品
sem_t empty_sem; // 同步信號量, 當滿了時阻止生產者放產品
sem_t full_sem;   // 同步信號量, 當沒產品時阻止消費者消費
pthread_mutex_t mutex; // 互斥信號量, 一次只有一個線程訪問緩衝
int product_id = 0;   //生產者id
int prochase_id = 0; //消費者id
/* 打印緩衝狀況 */
void print()
{
int i;
for(i = 0; i < M; i++)
   printf("%d ", buff[i]);
printf("\n");
}
/* 生產者方法 */ 
void *product()
{
int id = ++product_id;

while(1)
{
   // 用sleep的數量能夠調節生產和消費的速度,便於觀察
   sleep(1);
   //sleep(1);
  
   sem_wait(&empty_sem);
   pthread_mutex_lock(&mutex);
  
   in = in % M;
   printf("product%d in %d. like: \t", id, in);
  
   buff[in] = 1;  
   print();  
   ++in;
  
   pthread_mutex_unlock(&mutex);
   sem_post(&full_sem);  
}
}
/* 消費者方法 */
void *prochase()
{
int id = ++prochase_id;
while(1)
{
   // 用sleep的數量能夠調節生產和消費的速度,便於觀察
   sleep(1);
//sleep(1);
  
   sem_wait(&full_sem);
   pthread_mutex_lock(&mutex);
  
   out = out % M;
   printf("prochase%d in %d. like: \t", id, out);
  
   buff[out] = 0;
   print();
   ++out;
  
   pthread_mutex_unlock(&mutex);
   sem_post(&empty_sem);
}
}
int main()
{
pthread_t id1[N];
pthread_t id2[N];
int i;
int ret[N];

// 初始化同步信號量
int ini1 = sem_init(&empty_sem, 0, M); 
int ini2 = sem_init(&full_sem, 0, 0);  
if(ini1 && ini2 != 0)
{
   printf("sem init failed \n");
   exit(1);
} 
//初始化互斥信號量 
int ini3 = pthread_mutex_init(&mutex, NULL);
if(ini3 != 0)
{
   printf("mutex init failed \n");
   exit(1);
} 
// 建立N個生產者線程
for(i = 0; i < N; i++)
{
   ret[i] = pthread_create(&id1[i], NULL, product, (void *)(&i));
   if(ret[i] != 0)
   {
    printf("product%d creation failed \n", i);
    exit(1);
   }
}
//建立N個消費者線程
for(i = 0; i < N; i++)
{
   ret[i] = pthread_create(&id2[i], NULL, prochase, NULL);
   if(ret[i] != 0)
   {
    printf("prochase%d creation failed \n", i);
    exit(1);
   }
}
//銷燬線程
for(i = 0; i < N; i++)
{
   pthread_join(id1[i],NULL);
   pthread_join(id2[i],NULL);
}
exit(0); 
}

讀者-寫者問題

  • 寫者優先

當新的寫者但願寫時,不容許該寫者後續的讀者訪問數據區,但必須保證以前的讀者讀完。

1) 有寫者正在寫或者等待寫,須等到沒有寫者才能讀

2) 沒有寫者,能夠讀

3) 寫者與寫者互斥。當其它寫者正在寫時,其它寫者不能寫。

4) 寫者與讀者互斥。以前只有讀者在讀,當寫者出現時,必須等到以前的讀者都讀完才能寫。這尊重了以前讀者的意願。

5) 寫者能夠有條件地插讀者的隊。當前有寫者正寫,有讀者在等,這時來了新寫者,新寫者能夠在那些讀者以前執行。

代碼:

#include "stdio.h"  
#include <stdlib.h>  
#include <pthread.h>  
  
  
#define N_WRITER 2 //寫者數目  
#define N_READER 20 //讀者數目  
#define W_SLEEP 1 //控制寫頻率  
#define R_SLEEP  0.5 //控制讀頻率  
  
  
pthread_t wid[N_WRITER],rid[N_READER];  
const int MAX_RAND = 1000;//產生的最大隨機數  
int data = 0;  
int readerCnt = 0, writerCnt = 0;  
pthread_mutex_t accessReaderCnt = PTHREAD_MUTEX_INITIALIZER;  
pthread_mutex_t accessWriterCnt = PTHREAD_MUTEX_INITIALIZER;  
pthread_mutex_t writeLock = PTHREAD_MUTEX_INITIALIZER;  
pthread_mutex_t readerLock = PTHREAD_MUTEX_INITIALIZER;  
pthread_mutex_t outerLock = PTHREAD_MUTEX_INITIALIZER;  
  
void write()  
{  
    int rd = rand()%MAX_RAND;  
    printf("write %d\n",rd);  
    data = rd;  
}  
void read()  
{  
    printf("read %d\n",data);  
}  
void * writer(void * in)  
{  
    while(1)  
    {  
        pthread_mutex_lock(&accessWriterCnt);  
        {//臨界區,但願修改 writerCnt,獨佔 writerCnt  
            writerCnt++;  
            if(writerCnt == 1){  
                //阻止後續的讀者加入待讀隊列  
                pthread_mutex_lock(&readerLock);  
            }  
        }  
        pthread_mutex_unlock(&accessWriterCnt);  
          
          
        pthread_mutex_lock(&writeLock);  
        {//臨界區,限制只有一個寫者修改數據  
            write();  
        }  
        pthread_mutex_unlock(&writeLock);  
          
        pthread_mutex_lock(&accessWriterCnt);  
        {//臨界區,但願修改 writerCnt,獨佔 writerCnt  
            writerCnt--;  
            if(writerCnt == 0){  
                //阻止後續的讀者加入待讀隊列  
                pthread_mutex_unlock(&readerLock);  
            }  
        }  
        pthread_mutex_unlock(&accessWriterCnt);  
        sleep(W_SLEEP);  
    }  
    pthread_exit((void *) 0);  
}  
  
void * reader (void * in)  
{  
    while(1)  
    {  
        //假如寫者鎖定了readerLock,那麼成千上萬的讀者被鎖在這裏  
        pthread_mutex_lock(&outerLock);  
        {//臨界區  
            pthread_mutex_lock(&readerLock);//只被一個讀者佔有  
            {//臨界區  
                pthread_mutex_lock(&accessReaderCnt);//代碼段 1  
                {//臨界區  
                    readerCnt++;  
                    if(readerCnt == 1){  
                        pthread_mutex_lock(&writeLock);  
                    }  
                }  
                pthread_mutex_unlock(&accessReaderCnt);  
            }  
            pthread_mutex_unlock(&readerLock);//釋放時,寫者將優先得到readerLock  
        }  
        pthread_mutex_unlock(&outerLock);  
  
        read();  
          
        pthread_mutex_lock(&accessReaderCnt);//代碼段2  
        {//臨界區  
            readerCnt--;  
            if(readerCnt == 0){  
                pthread_mutex_unlock(&writeLock);//在最後一個併發讀者讀完這裏開始禁止寫者執行寫操做  
            }  
        }  
        pthread_mutex_unlock(&accessReaderCnt);  
          
        sleep(R_SLEEP);  
    }  
    pthread_exit((void *) 0);  
}  
  
int main()  
{  
    int i = 0;  
    for(i = 0; i < N_READER; i++)  
    {  
        pthread_create(&rid[i],NULL,reader,NULL);  
    }  
    for(i = 0; i < N_WRITER; i++)  
    {  
        pthread_create(&wid[i],NULL,writer,NULL);  
    }  
    while(1){  
        sleep(10);  
    }  
    return 0;  
}

練習

  • 課本練習題12.1: 在下圖中,併發服務器的第33行上,父進程關閉了已鏈接描述符後,子進程仍可以使用該描述符和客戶端通訊,爲何?

image

  • 課本練習題12-1解決:父進程派生子進程時,相關文件表中的引用計數從1增長到2。父進程一關閉描述符副本,引用計數就由2減小到1,由於內核不會關閉一個文件,直到文件表中的引用計數值變爲0,因此子進程這邊的鏈接端將保持打開。

  • 課本練習題12.5:在下圖1中基於進程的服務器中,咱們在兩個位置當心地關閉了已鏈接描述符:父進程和子進程。然而,在圖2中,基於線程的服務器中,咱們只在一個位置關閉了已鏈接描述符:對等線程,爲何?

image

image

  • 課本練習題12-5解決:由於線程運行在同一個進程中,他們共享同一個描述符表,因此在描述符表中的引用計數與線程的多少是沒有關係的都爲1,所以只須要一個close就夠了。

  • 課本練習題12.10下圖所示的對第一類讀者-寫者問題的解答給予讀者較高的優先級,可是從某種意義上說,這種優先級是很弱的,由於一個離開臨界區的寫者可能重啓一個在等待的寫者,而不是一個在等待的讀者。描述一個場景,其中這種弱優先級會致使一羣寫者使得一個讀者飢餓。

image

  • 課本練習題12-10解決:假設一個特殊的信號量實現爲每個信號量使用了一個LIFO的線程棧。當一個線程在P操做中阻塞在一個信號量上,它的ID就被壓入棧中。相似地,V操做從棧中彈出棧頂的線程ID,並重啓這個線程。根據這個棧的實現,一個在它的臨界區中競爭的寫者會簡單的等待,直到在他釋放這個信號量以前另外一個寫者阻塞在這個信號量上。在這種場景中,當兩個寫者來回地傳遞控制權時,正在等待的讀者可能會永遠的等待下去。

教材學習中的問題和解決過程

  • 問題1:顯式終止與隱式終止的區別
  • 問題1解決方案:顯示終止:就是天然終止或者顯式調用ExitTread,隱式終止就是隱式調用pthread_exit。

其餘問題加在上文中

代碼調試中的問題和解決過程

在上文中

代碼託管

結對及互評

本週結對學習狀況

  • 20155326
    • 結對照片

    • 結對學習內容

      • 第四章內容

其餘(感悟、思考等,可選)

本週再次學習了多線程、進程和併發的相關內容,雖然當時在操做系統也學習了,本身也學習了,可是此次再學的時候仍是發現問題多多,在不少的理解方面都不細緻,和結對對象討論也會發現一些新的思路,並且感受雖然在JAVA上也對相應內容進行了學習,可是在多線程多進程方面老是理不清的感受,此次一塊兒邊實踐邊回顧,感受此次學下來,效果更好,收穫仍是蠻多的。

學習進度條

代碼行數(新增/累積) 博客量(新增/累積) 學習時間(新增/累積) 重要成長
目標 5000行 30篇 200小時
第一週 5/5 1/1 15/15
第二週 1/2 23/38
第三週 206/327 1/3 28/66
第四周 206/327 1/4 10/77
第五週 285/568 1/5 20/97 主要學習了彙編及反彙編的相關知識
第六週 160/683 3/8 20/107
第七週 / 2/10 20/127 第四章學習內容和第二次實驗
第八週 2/12 22/149 第十一章、第十二章
第九周 408/ 2582 3/15 21/170 第六章內容、第三次實驗、課後pwd的實現
第十一週 174 / 3035 3/18 20/200 第九章內容、實驗四
第十三週 741/3776 2/22 20/220 第十二章再學習

嘗試一下記錄「計劃學習時間」和「實際學習時間」,到期末看看能不能改進本身的計劃能力。這個工做學習中很重要,也頗有用。
耗時估計的公式
:Y=X+X/N ,Y=X-X/N,訓練次數多了,X、Y就接近了。

參考:軟件工程軟件的估計爲何這麼難軟件工程 估計方法

相關文章
相關標籤/搜索