標籤:
多線程編程
作業系統原理概念
時間片
進程狀態
上下文: 對進程來說,就是進程的執行環境,具體就是各個變數和資料,包括所有的寄存器變數、開啟的檔案、記憶體資訊等。
進程的寫時複製:由於一般 fork後面都接著exec,所以,現在的 fork都在用寫時複製的技術,顧名思意,就是,資料區段,堆,棧,一開始並不複製,由父,子進程共用,並將這些記憶體設定為唯讀。直到父,子進程一方嘗試寫這些地區,則核心才為需要修改的那片記憶體拷貝副本。這樣做可以提高 fork的效率。
線程函數的可重新進入性:所謂“重入”,常見的情況是,程式執行到某個函數foo()時,收到訊號,於是暫停目前正在執行的函數,轉到訊號處理函數,而這個訊號處理函數的執行過程中,又恰恰也會進入到剛剛執行的函數foo(),這樣便發生了所謂的重入。此時如果foo()能夠正確的運行,而且處理完成後,之前暫停foo()也能夠正確運行,則說明它是可重新進入的。
要確保函數可重新進入,需滿足一下幾個條件:
1、不在函數內部使用靜態或全域資料
2、不返回靜態或全域資料,所有資料都由函數的調用者提供。
3、使用本機資料,或者通過製作全域資料的本地拷貝來保護全域資料。
4、不調用不可重新進入函數。
********************************************************************
線程與進程的對比
進程是具有一定獨立功能的程式關於某個資料集合上的一次運行活動,是系統進行資源分派和調度的一個獨立單位。
線程是進程中執行運算的最小單位,是進程中的一個實體,是被系統獨立調度和指派的基本單位,線程自己不擁有系統資源,只擁有一點在運行中必不可少的資源,但它可與同屬一個進程的其它線程共用進程所擁有的全部資源。一個線程可以建立和撤消另一個線程,同一進程中的多個線程之間可以並發執行。
進程和線程的關係:
(1)一個線程只能屬於一個進程,而一個進程可以有多個線程,但至少有一個線程。
(2)資源分派給進程,同一進程的所有線程共用該進程的所有資源。
(3)處理機分給線程,即真正在處理機上啟動並執行是線程。
(4)線程在執行過程中,需要協作同步。不同進程的線程間要利用訊息通訊的辦法實現同步。線程是指進程內的一個執行單元,也是進程內的可調度實體.
進程與線程的區別:
(1)調度:線程作為調度和分配的基本單位,進程作為擁有資源的基本單位
(2)並發性:不僅進程之間可以並發執行,同一個進程的多個線程之間也可並發執行
(3)擁有資源:進程是擁有資源的一個獨立單位,線程不擁有系統資源,但可以訪問隸屬於進程的資源.
(4)系統開銷:在建立或撤消進程時,由於系統都要為之分配和回收資源,導致系統的開銷明顯大於建立或撤消線程時的開銷。
線程相對於進程的優勢:
(調度、分配:更少的產生刪除時間
進程/線程之間的切換開銷(掛起正在啟動並執行進程或線程,恢複以前掛起的進程或線程):線程切換更快,不用恢複使用者地址空間
通訊機制:通訊更有效(共用地址空間、不需要調用核心傳遞資訊)
並發性
編碼之間的原理:建立過程的複雜度,及對程式的控制力度)
(1)易於調度。
(2)提高並發性。通過線程可方便有效地實現並發性。進程可建立多個線程來執行同一程式的不同部分。
(3)開銷少。建立線程比建立進程要快,所需開銷很少。。
(4)利於充分發揮多處理器的功能。通過建立多線程進程,每個線程在一個處理器上運行,從而實現應用程式的並發性,使每個處理器都得到充分運行。
進程與線程的狀態對比
進程狀態
狀態:運行、阻塞、掛起阻塞、就緒、掛起就緒
狀態之間的轉換:
準備就緒的進程,被CPU調度執行,變成運行態;
運行中的進程,進行I/O請求或者不能得到所請求的資源,變成阻塞態;
運行中的進程,進程執行完畢(或時間片已到),變成就緒態;
將阻塞態的進程掛起,變成掛起阻塞態,當導致進程阻塞的I/O操作在使用者重啟進程前完成(稱之為喚醒),掛起阻塞態變成掛起就緒態,當使用者在I/O操作結束之前重啟進程,掛起阻塞態變成阻塞態;
將就緒(或運行)中的進程掛起,變成掛起就緒態,當該進程恢複之後,掛起就緒態變成就緒態;
線程狀態
同步和互斥的區別:
當有多個線程的時候,經常需要去同步這些線程以訪問同一個資料或資源。例如,假設有一個程式,其中一個線程用於把檔案讀到記憶體,而另一個線程用於統計檔案中的字元數。當然,在把整個檔案調入記憶體之前,統計它的計數是沒有意義的。但是,由於每個操作都有自己的線程,作業系統會把兩個線程當作是互不相干的任務分別執行,這樣就可能在沒有把整個檔案裝入記憶體時統計字數。為解決此問題,你必須使兩個線程同步工作。
所謂同步,是指散步在不同進程之間的若干程式片斷,它們的運行必須嚴格按照規定的某種先後次序來運行,這種先後次序依賴於要完成的特定的任務。如果用對資源的訪問來定義的話,同步是指在互斥的基礎上(大多數情況),通過其它機制實現訪問者對資源的有序訪問。在大多數情況下,同步已經實現了互斥,特別是所有寫入資源的情況必定是互斥的。少數情況是指可以允許多個訪問者同時訪問資源。
所謂互斥,是指散布在不同進程之間的若干程式片斷,當某個進程運行其中一個程式片段時,其它進程就不能運行它們之中的任一程式片段,只能等到該進程運行完這個程式片段後才可以運行。如果用對資源的訪問來定義的話,互斥某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的。
********************************************************************
線程的操作(根據生命週期的圖示操作)
線程的建立
#include <pthread.h>int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);// compile and link with -pthread
thread: 指向線程標識符的指標
attr: 設定線程的屬性
start_routine: 線程運行程式的起始地址
arg: 運行程式的參數
void* arg : 無法通過該參數傳輸大量資料
解決方案:
為每個線程定義一個結構,結構中包括同一線程函數所需的所有資料(易於實現多線程複用同一線程函數);
等待指定線程的結束
#include <pthread.h>int pthread_join(pthread_t thread, void **retval);
當一個進程建立的時候,一個主線程將被建立
主線程持有進程資訊,主線程與新建立進程間沒有顯示的父子關係
函數返回時,被等線程的資源被回收(與進程不同)
一個線程不能被多個線程等待:如果這樣的話第一個收到訊號的線程成功返回,其他的出錯ESRCH;
注意線程等待
線程的結束
#include <pthread.h>void pthread_exit(void *retval);
線程的原子操作(例如銀行取錢系統的操作)
進程中沒有原子操作
非同步可刪除(線程初始化時預設是非同步可刪除的)
同步可刪除
包含線程的程式的GCC編譯
在編譯參數中加入 -pthread
線程id跟進程id
進程id是可移植的,是unsigned int 類型.線程id是一個非可移植性的類型,也就是說,在這個系統中,可能是unsigned int類型,在別的系統可能是long,double,或者甚至就是個結構體。所以,為了準確起見,列印的時候,表示為十六進位的話,也就是:printf("threadId is %x \n",threadId[i]); 如我們的系統獲得,pthread_t是個結構體時,我們可以把這個值直接認為是十六進位的地址值.
線程應用執行個體
web伺服器
單線程伺服器(只有一個主線程)
多線程伺服器(一個主線程產生多個背景工作執行緒)
一個主線程對應一個連接埠,故程式一般只佔有一個主線程。
線程池伺服器(一個主線程建立一個線程池,從線程池中取出線程用於處理使用者請求)
進程的動態性
進程、訊號的生命週期
線程的時間片
課堂練習:
新線程與主線程共用記憶體,主線程等待停到i=0的狀態,四個新線程都建立完成了
主線程可能還停留在i=0的狀態。然後新線程列印出四個0.
解決方案:設定新線程不共用記憶體,每個都有獨立的記憶體空間。
多線程編程中互斥鎖的使用
Linux系統編程@多線程編程