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

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

本章要點

  • 併發:若是邏輯流在時間上重疊,那麼他們就是併發的,硬件異常處理程序、進程和UNIX信號處理程序都是熟悉的例子。並發現象不只在內核中存在,在應用級別的程序中也存在
  • 三種基本的構造併發程序的方法:html

    1)進程。每一個邏輯控制流都是一個進程,由內核來調度和維護git

    2)I/O多路複用程序員

    3)線程編程

  • 基於進程的併發 echo 服務器.父進程派生一個子進程來處理每一個新的鏈接請求
  • 進程可以共享文件表,但不共享用戶地址空間
  • 基於I/O多路複用的併發編程:安全

    面對困境——服務器必須響應兩個互相獨立的I/O事件:服務器

    1)網絡客戶端發起的鏈接請求網絡

    2)用戶在鍵盤上鍵入的命令 ,解決的辦法是I/O多路複用技術。
    基本思想是,使用select函數,要求內核掛起進程,只有在一個或多個I/O事件發生後,纔將控制返回給應用程序。
  • select函數:
    數據結構

select函數處理類型爲fd_set的集合,即描述符集合,並在邏輯上描述爲一個大小爲n的位向量,每一位b[k]對應描述符k,但當且僅當b[k]=1,描述符k才代表是描述符集合的一個元素。多線程

使用select函數的過程以下:併發

第一步,初始化fd_set集,19~22行;

第二步,調用select,25行;

第三步,根據fd_set集合如今的值,判斷是哪一種I/O事件,26~31行。

  • 基於I/O多路複用的併發事件驅動服務器:I/O多路複用能夠用作併發事件驅動程序的基礎,在事件驅動程序中,流是由於某種事件而前進的,通常概念是把邏輯流模型化爲狀態機。一個狀態機就是一組狀態、輸入事件和轉移。
    併發事件驅動程序中echo服務器中邏輯流的狀態機,以下圖所示:

  • 線程:運行在進程上下文中的邏輯流。線程由內核自動調度,每一個線程都有它本身的線程上下文。
  • 線程上下文包括:一個惟一的整數線程ID——TID、棧、棧指針、程序計數器、通用目的寄存器、條件碼
  • 線程執行模型

  • 建立線程,調用pthread_create函數來建立其餘線程,pthread_create函數建立一個新的線程,帶着一個輸入變量arg,在新線程的上下文運行線程例程f。
    attr默認爲NULL

# include <pthread.h>
typedef void *(func)(void *);

int pthread_create(pthread_t *tid, pthread_attr_t *attr, func *f, void *arg);

返回:成功返回0,出錯返回非0
  • 新線程調用pthread_self函數來得到本身的線程ID
#include <pthread.h>

pthread_t pthread_self(void);

返回調用者的線程ID(TID)
  • 終止線程的幾個方式:

1)當頂層的線程例程返回時,線程會隱式終止;

2)線程調用pthread_exit函數,線程會顯示終止;若是主線程調用pthread_exit,它會等到全部其餘對等線程終止,而後再終止主線程和整個線程,返回值爲thread_return;

pthread_exit函數

#include <pthread.h>

void pthread_exit(void *thread_return);
若成功返回0,出錯爲非0

3)某個對等線程調用exut函數,則函數終止進程和全部與該進程相關的線程;

4)另外一個對等線程調用以當前ID爲參數的函數ptherad_cancel來終止當前線程

#include <pthread.h>

void pthread_cancle(pthread_t tid);
若成功返回0,出錯爲非0
  • 調用pthread_join函數回收已終止線程的資源

這個函數會阻塞,直到線程tid終止,將線程例程返回的(void*)指針賦值爲thread_return指向的位置,而後回收已終止線程佔用的全部存儲器資源

  • 分離線程
    在任何一個時間點上,線程是可結合的,或是分離的。
    一個可結合的線程可以被其餘線程回收其資源和殺死,在被其餘線程回收以前,它的存儲其資源是沒有被釋放的;相反,一個分離的線程是不能被其餘線程回收或殺死的。它的存儲器資源是在它終止時系統自動釋放的。默認狀況下,線程被建立成可結合的。但現實程序中,有很好的理由要使用分離線程。
  • 初始化線程:pthread_once函數
  • 一個基於線程的併發服務器

  • 藉助進度圖

    1.進度圖是將n個併發線程的執行模型化爲一條n維笛卡爾空間中的軌跡線,原點對應於沒有任何線程完成一條指令的初始狀態。
    當n=2時,狀態比較簡單,是比較熟悉的二維座標圖,橫縱座標各表明一個線程,而轉換被表示爲有向邊

    2.進度圖將指令執行模型化爲從一種狀態到另外一種狀態的轉換(transition)。轉換被表示爲一條從一點到相鄰點的有向邊。合法的轉換是向右(線程 1 中的一條指令完成〉或者向上(線程 2 中的一條指令完成)的。兩條指令不能在同一時刻完成一一對角線轉換是不容許的。程序決不會反向運行,因此向下或者向左移動的轉換也是不合法的。

  • 一個程序的執行歷史被模型化爲狀態空間中的一條軌跡線。

  • 臨界區:對於線程i,操做共享變量cnt內容的指令L,U,S構成了一個關於共享變量cnt的臨界區。

    不安全區:兩個臨界區的交集造成的狀態

    安全軌跡線:繞開不安全區的軌跡線,繞開不安全區的軌跡線叫作安全軌跡線 (safe trajectory)。相反,接觸到任何不安全區的軌跡線就叫作不安全軌跡線 (unsafe trajectory)。

  • 一種解決同步不一樣執行線程問題的方法,這種方法是基於一種叫作信號量 (semaphore) 的特殊類型變量的。信號量 s 是具備非負整數值的全 局變量,只能由兩種特殊的操做來處理,這兩種操做稱爲 P 和 V:
    經過調用 sem_wait 和 sem_post 函數來執行P和V操做。

    1)P(s):若是s是非零的,那麼P將s減1,而且當即返回。若是s爲零,那麼就掛起這個線程,直到s變爲非零,而一個V操做會重啓這個線程。在重啓以後,P 操做將s減1,並將控制返回給調用者。

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

  • 信號量的函數

  • P和V的包裝函數

  • 使用信號量來實現互斥
    基本思想:將每一個共享變量(或者一組相關的共享變量)與一個信號量s(初始爲1)聯繫起來,而後用P和V操做將相應的臨界區包圍起來。

  • 利用信號量來調度共享資源,信號量有兩個做用:

    1)實現互斥

    2)調度共享資源

  • 使用線程提升並行性

順序、併發和並行程序集合之間的關係

  • 並行程序的加速比 一般定義爲,其中p 是處理器核的數量,凡是在 k個核上的運行時間。這個公式有時稱爲強擴展 (strong scaling)。當 T1 是程序順序執行版本的執行時間時, Sp 稱爲絕對加速比.(absolute speedup)。當 T1 是程序並行版本在一個核上的執行時間時, Sp 稱爲相對加速比 (relative speedup)。絕對加速 比比相對加速比能更真實地衡量並行的好處。

  • 效率被定義如圖所示,它一般表示爲範圍在 (0, 100] 之間的百分比。效率是對因爲並行化形成的開銷的衡量。具備高 效率的程序比效率低的程序在有用的工做上花費更多的時間,在同步和通訊上花費更少的時間。

  • 線程安全是指一個函數被稱爲線程安全的,當且僅當被多個併發線程反覆的調用時,它會一直產生正確的結果
  • 可重入函數,其特色在於它們具備這樣一種屬性:當它們被多個線程調用時,不會引用任何共享數據。

    可重入函數一般要比不可重人的線程安全的函數高效一些,由於它們不須要同步操做。更進一步來講,將第2類線程不安全函數轉化爲線程安全函數的惟一方法就是重寫它,使之變爲可重入的。

    可重入函數分爲兩類:
    1)顯式可重入的:全部函數參數都是傳值傳遞,沒有指針,而且全部的數據引用都是本地的自動棧變量,沒有引用靜態或全劇變量。

    2)隱式可重入的:調用線程當心的傳遞指向非共享數據的指針。

  • 競爭是因爲一個程序的正確性依賴於一個線程要在另外一個線程到達y點以前到達它的控制流中的x點。一般發生競爭是由於程序員假定線程會按照某種特殊的軌跡穿過執行狀態空間,忘了一條準則規定:線程化的程序必須對任何可行的軌跡線都正確工做。
  • 消除競爭的方法:動態的爲每一個整數ID分配一個獨立的塊,而且傳遞給線程例程一個指向這個塊的指針。

課下練習

  • 12.1 在圖12-5中,併發服務器的第33行上,父進程關閉了已鏈接描述符後,子進程仍可以使用該描述符和客戶端通訊,爲何。

    :當父進程派生子進程時,它獲得一個已鏈接描述符的副本,並將相關文件表中的引用計數從1增長到2.當父進程關閉它的描述符副本時,引用計數就從2減小到1.由於內核不會關閉一個文件,知道文件表中它的引用計數值變爲0,因此子進程這邊的鏈接端將保持打開。
  • 12.2 若是咱們要刪除圖12-5中關閉已鏈接描述符的第30行,從沒有內存泄漏的角度來講,代碼將仍然是正確的,爲何?

    :當一個進程由於某種緣由終止時,內核將關閉全部打開的描述符。所以,當子進程退出時,它的已鏈接文件描述符的副本也將被自動關閉。
  • 12.3 在Linux系統裏,在標準輸入上鍵入Ctrl+D表示EOF。圖12-6中的程序阻塞在對select的調用上,若是你鍵入Ctrl+D會發生什麼

    :若是一個從描述符中讀一個字節的請求不會阻塞,那麼這個描述符就準備好能夠讀了。假如EOF在一個描述符上爲真,那麼描述符也準備好可讀了,由於讀操做將當即返回一個零返回碼,表示EOF。所以,鍵入Ctrl+D會致使select函數返回,準備好的集合中有描述符0.
  • 12.4 圖12-8所示的服務器中,咱們在每次調用select以前都當即當心地從新初始化pool.ready_set變量,爲何?

    :由於變量pool.read_set既做爲輸入參數,也做爲輸出參數,因此咱們在每一次調用select以前都從新初始化它。在輸入時,它包含讀集合。在輸出時,它包含準備好的集合。
  • 12.5 在圖12-5中基於進程的服務器中,咱們在兩個位置當心地關閉了已鏈接描述符:父進程和子進程。然而,在圖12-14中,基於線程的服務器中,咱們只在一個位置關閉了已鏈接描述符:對等線程,爲何?

:由於線程運行在同一個進程中,它們都共享相同的描述符表。不管有多少線程使用這個已鏈接描述符,這個已鏈接描述符的文件表的引用計數都等於1.所以,當咱們用完它時,一個close操做就足以釋放於這個已鏈接描述符相關的內存資源了。

  • 12.6

    A.利用12.4節中的分析,爲圖12-15中的示例程序在下表的每一個條目中填寫「是」或者「否」。在第一列中,符號v,t表示變量v的一個實例,它駐留在線程t的本地棧中,其中t要麼是m(主線程),要麼是p0(對等線程)或者p1(對等線程1)

B.根據A部分的分析,變量ptr、cnt、i、msgs和myid那些是共享的

:變量ptr、cnt、msgs被多於一個線程引用,因此它們是共享的。

  • 12.7 根據badcnt.c的指令順序完成下表,這種順序會產生一個正確的值嗎?

    變量cnt最終有一個不正確的值1
  • 12.8 使用圖12-21中的進度圖,將下列軌跡線劃分爲安全的或者不安全的

    A.H1,L1,U1,S1,H2,L2,U2,S2,T2,T1

    B.H2,L2,H1,L1,U1,S1,T1,U2,S2,T2

    C.H1,H2,L2,U2,S2,L1,U1,S1,T1,T2

    A.H1,L1,U1,S1,H2,L2,U2,S2,T2,T1是安全的

    B.H2,L2,H1,L1,U1,S1,T1,U2,S2,T2是不安全的

    C.H1,H2,L2,U2,S2,L1,U1,S1,T1,T2是安全的
  • 12.9 設p表示生產者數量,c表示消費者數量,而n表示以項目單元爲單位的緩衝區大小。對於下面的每一個場景,指出sbuf_insert和sbuf_remove中的互斥鎖信號量是不是必需的。

    A.p=1,c=1,n>1

    B.p=1,c=1,n=1

    C.p>1,c>1,n=1

    :

    A.互斥鎖是須要的,由於生產者和消費者會併發地訪問緩衝區

    B.不須要互斥鎖,由於一個非空的緩衝區就等於滿的緩衝區。當緩衝區包含一個項目時,生產者就別阻塞了,當緩衝區爲空時,消費者就被阻塞了。因此在任意時刻,只有一個線程能夠訪問緩衝區,所以不用互斥鎖也能保證互斥

    C.不須要互斥鎖,緣由與B相同。
  • 12.10 圖12-26所示的對第一類讀者-寫者問題的解答給予讀者較高的優先級,可是從某種意義上說,這種優先級是很弱的,由於一個離開臨界區的寫者可能重啓一個在等待的寫者,而不是一個在等待的讀者。描述一個場景,其中這種弱優先級會致使一羣寫者使得一個讀者飢餓。

    :假設一個特殊的信號量實現爲每個信號量使用了一個LIFO的線程棧。當一個線程在P操做中阻塞在一個信號量上,它的ID就被壓入棧中。相似地,V操做從棧中彈出棧頂的線程ID,並重啓這個線程。根據這個棧的實現,一個在它的臨界區中競爭的寫者會簡單的等待,直到在他釋放這個信號量以前另外一個寫者阻塞在這個信號量上。在這種場景中,當兩個寫者來回地傳遞控制權時,正在等待的讀者可能會永遠的等待下去。
  • 12.11 對於下表中的並行程序,填寫空白處。假設使用強擴展。image

  • 12.12 圖12-38中的ctime_ts函數是線程安全的,但不是可重入的,請解釋說明。

    :ctime_ts函數不是可重入函數,由於每次調用都共享相同的由ctime函數返回的static變量。然而,它是線程安全的,由於對共享變量的訪問是被P和V操做保護的,所以是互斥的。
  • 12.13 在圖12-43中,咱們可能想要在主線程中的第14行後當即釋放已分配的內存塊,而不是在對等線程中釋放它。可是這會是個壞主意,爲何?

    :若是在第14行調用了pthread_create以後,咱們當即釋放塊,那麼將引入一個新的競爭,此次競爭發生在主線程對free的調用和線程例程中第24行的賦值語句之間。
  • 12.14

    A.在圖12-43中,咱們經過爲每一個整數ID分配一個獨立的塊來消除競爭。給出一個不調用malloc或者free函數的不一樣的方法。

    :另外一種方法是直接傳遞整數i,而不是傳遞一個指向i的指針:

for(i=0;i<N;i++)
    Pthread_create(&tid[i],NULL,thread,(void*)i);

在線程例程中,咱們將參數強制轉換成一個int型變量,並將它賦值給myid;

int myid=(int)vargp;

B.這種方法的利弊是什麼?

:優勢是它經過消除對malloc和free的調用下降了開銷。一個明顯的缺點是,它假設指針至少和int同樣大。即使這種假設對於全部得現代系統來講都爲真,可是它對於那些過去遺留下來的或從此的系統來講可能就不爲真了。

  • 12.15 思考下面的程序,它試圖使用一對信號量來實現互斥。
    image
    A.畫出這個程序的進度圖。

B.它老是會死鎖嗎

:會,由於任何可行的軌跡最終都陷入思索狀態。

C.若是是,那麼對初始信號量的值作哪些簡單的改變就能消除這種潛在的死鎖呢?

:爲了消除潛在的死鎖,將二元信號量t初始化爲1而不是0。

D.畫出獲得的無死鎖程序的進度圖。

家庭做業

  • 12.16 編寫一個hello.c,他建立和回收n個可結合的對等線程,其中n是一個命令行參數。
    代碼以下
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<unistd.h>
#include<pthread.h>
void *thread(void *vargp);
int main(int argc, char **argv)
{
    pthread_t tid;
    int i,n;
    n = atoi(argv[1]); 
    for(i=0;i<n;i++)
    {
        pthread_create(&tid,NULL,thread,NULL);
        pthread_join(tid,NULL);
    }
    exit(0);
}
void *thread(void *vargp)
{
    printf("hello world!\n");
    return NULL;
}
  • 12.17 運行以下代碼
    A.並不會輸出字符,是爲何?
    B.經過是用什麼函數來改正這個錯誤呢?
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<unistd.h>
#include<pthread.h>
void *thread(void *vargp);
int main(int argc, char **argv)
{
    pthread_t tid;
    int i,n;
    pthread_create(&tid,NULL,thread,NULL);
    exit(0);
}
void *thread(void *vargp)
{
    sleep(1);
    printf("hello world!\n");
    return NULL;
}
  • :A.是由於exit(0),使得線程在主線程中結束了,因此沒有打印出字符。
    B.使用pthread_join就能夠解決這個問題

  • 12.18 用進度圖說明下面的軌跡分類是否安全:
    A.H2,L2,U2,H1,L1,S2,U1,S1,T1,T2

    B.H2,H1,L1,U1,S1,L2,T1,U2,S2,T2

    C.H1,L1,H2,L2,U2,S2,U1,S1,T1,T2

:結果:A不安全,B安全,C安全

  • 12.19 教材p707給出的代碼,給予了讀者比較弱的優先級,當讀者離開緩衝區時,可能會重啓一個正在等待的寫着,而不是一個正在等待的讀者,試着更改代碼,使得讀者優先。
    信號量代碼以下
int readcnt;
sem_t mutex=1,w=1,z=1;
void reader(void)
{
    while(1)
    {
        P(&mutex);
        readcnt++;
        if(readcnt==1)
        P(&w);
        V(&mutex);
        
        P(&mutex);
        readcnt--;
        if(readcnt==0)
        V(&w);
        V(&mutex);
    }
}

void writer(void)
{
    while(1)
    {
        P(&z);
        P(&w);
        V(&w);
        V(&z);
    }
}
  • 12.22 檢查對select函數的理解,修改下圖所示服務器使得它在主服務器每次迭代中至多隻會送一個文本:

    :在while循環中增長一個FD_AERO(&ready_set);就能夠了。

  • 12.24 RIO I/O包中的函數都是線程安全的,那麼他們都是是可重入的嗎?
    :是不可重入的,RIO包中有專門的數據結構爲每個文件描述符都分配了相應的獨立的讀緩衝區,它提供了與系統I/O相似的函數接口,在讀取操做時,RIO包加入了讀緩衝區,必定程度上增長了程序的讀取效率。另外,帶緩衝的輸入函數是線程安全的,這與Stevens的 UNP 3rd Edition(中文版) P74 中介紹的那個輸入函數不一樣。UNP的那個版本的帶緩衝的輸入函數的緩衝區是以靜態全局變量存在,因此對於多線程來講是不可重入的。

  • 12.29 下面的程序會死鎖嗎?爲何?
    初始時:a=1;b=1;c=1
    線程1 : 線程2:
    p(a); p(c);
    p(b); p(b);
    v(b); v(b);
    p(c); v(c);
    v(c);
    v(a);
    : 會死鎖,由於線程1和2在執行完第一步以後都被掛起,都得不到須要的資源。

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

(一個模板:我看了這一段文字 (引用文字),有這個問題 (提出問題)。 我查了資料,有這些說法(引用說法),根據個人實踐,我獲得這些經驗(描述本身的經驗)。 可是我仍是不太懂,個人困惑是(說明困惑)。【或者】我反對做者的觀點(提出做者的觀點,本身的觀點,以及理由)。 )

  • 問題1:對於教材中的問題關於重入的部分不是很理解。
  • 問題1解決方案:上網搜索,知道了什麼是重入什麼是不重入,所謂可重入是指一個能夠被多個任務調用的過程,任務在調用時沒必要擔憂數據是否會 出錯。不可重入函數在實時系統設計中被視爲不安全函數。

知足下列條件的函數多數是不可重入的:
(1)函數體內使用了靜態的數據結構;
(2)函數體內調用了malloc()或者free()函數;
(3)函數體內調用了標準I/O函數。
詳細能夠參考連接提供的內容。

  • 問題2:XXXXXX
  • 問題2解決方案:XXXXXX
  • ...

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

  • 問題1:在線程的編譯過程當中出現下圖所示問題
  • 問題1解決方案:忘記了要加-lpthread編譯,因爲pthread 庫不是 Linux 系統默認的庫,鏈接時須要使用靜態庫 libpthread.a,因此在使用pthread_create()建立線程,以及調用 pthread_atfork()函數創建fork處理程序時,須要連接該庫。

代碼託管

(statistics.sh腳本的運行結果截圖)

上週考試錯題總結

  • 錯題1及緣由,理解狀況
  • 錯題2及緣由,理解狀況
  • ...

結對及互評

點評模板:

  • 博客中值得學習的或問題:
    • xxx
    • xxx
    • ...
  • 代碼中值得學習的或問題:
    • xxx
    • xxx
    • ...
  • 其餘

本週結對學習狀況

- 20155301](https://home.cnblogs.com/u/fengxingck/)
- 結對照片
- 結對學習內容
    - 共同窗習了第12章

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

xxx
xxx

學習進度條

代碼行數(新增/累積) 博客量(新增/累積) 學習時間(新增/累積) 重要成長
目標 5000行 30篇 400小時
第一週 200/200 2/2 20/20
第二週 300/500 2/4 18/38
第三週 500/1000 3/7 22/60
第四周 300/1300 2/9 30/90

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

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

  • 計劃學習時間:XX小時

  • 實際學習時間:XX小時

  • 改進狀況:

(有空多看看現代軟件工程 課件
軟件工程師能力自我評價表
)

參考資料

相關文章
相關標籤/搜索