linux和windows多執行緒的異同
linux多執行緒及執行緒同步和windows的多執行緒之間的異同
並不是所有的程式都必須採用多執行緒,有時候採用多執行緒效能還不如單執行緒。採用多執行緒的好處如下:
(1)多執行緒之間採用相同的地址空間,共享大部分的資料,和多程序相比,代價比較節儉,而啟動新的程序必須分配給它獨立的地址空間,需要資料表來維護程式碼段,資料段和堆疊段等等。
(2)對不同程序來說,它們具有獨立的資料空間,要進行資料的傳遞只能通過通訊的方式進行,費時而且不方便。多執行緒之間可以直接共享資料,比如共享全域性變數。共享全域性變數要注意變數的同步性,不然容易引起災難性的後果。
(3)在多cpu的情況下,不同的執行緒可以執行在不同的cpu下,這樣就完全並行了。
在這種情況下,採用多執行緒比較理想。比如要做一個任務分2個步驟,為提高工作效率可以多執行緒技術開闢2個執行緒,第一個執行緒做第一步,第2個執行緒做第2步。這個時候要注意同步。因為只有第一步做完才能做第2步的工作。這時,可以採用同步技術進行執行緒之間的通訊。
針對這種情況,講解一下多執行緒之間的通訊,在windows平臺下,多執行緒之間通訊採用的方法主要有:
(1)共享全域性變數,比如上面的問題,第一步要向第2步傳遞收據,可以共享全域性變數,讓兩個執行緒之間傳遞資料,這時主要考慮的問題就是變數的同步,因為後面的執行緒在對資料進行操作的時候,第一個執行緒又改變了資料的內容,不同步保護,後果很嚴重(即讀回髒資料)。這種情況下,容易想到的同步方法是設定一個bool flag,比如在第2個執行緒還沒有用完資料前,第一個執行緒不能寫入。有時在2個執行緒所需的時間不相同的時候,怎樣達到最大效率的同步,就比較麻煩。 這時可以多開幾個緩衝區進行操作。如果是2個執行緒一直在跑,由於時間不一致,緩衝區遲早會溢位。在這種情況下要考慮:是不讓資料寫入還是讓資料覆蓋掉舊的資料。這時候要具體問題具體分析。即用bool變數控制同步,linux 和windows是一樣的。
同樣針對上面的這個問題,共享全域性變數同步問題。除了採用bool變數外,還有互斥量。即加鎖。windows下加鎖和linux下加鎖是類似的。採用互斥量進行同步,要想進入那段程式碼,就先必須獲得互斥量。
windows下互斥量的函式有:createmutex()建立一個互斥量,然後就是獲得互斥量waitforsingleobject()函式,用完了就釋放互斥量ReleaseMutex(hMutex),當減到 0的時候 核心會才會釋放其物件。下面是windows下與互斥的幾個函式原型。
HANDLE WINAPI CreateMutex(
__in LPSECURITY_ATTRIBUTES lpMutexAttributes,
__in BOOL bInitialOwner,
__in LPCTSTR lpName
);
可以可用來建立一個有名或無名的互斥量物件
第一引數 可以指向一個結構體SECURITY_ATTRIBUTES 一般可以設為null;
第二引數 指當時的函式是不是感應感應狀態 FALSE為當前擁有者不會建立互斥
第三引數 指明是否是有名的互斥物件 如果是無名 用null就好。
DWORD WINAPI WaitForSingleObject(
__in HANDLE hHandle,
__in DWORD dwMilliseconds
);
第一個是 建立的互斥物件的控制代碼。第二個是 表示將在多少時間之後返回 如果設為巨集INFINITE 則不會返回 直到使用者自己定義返回。
對於linux作業系統,互斥也是類似的,只是函式不同罷了。在linux下,和互斥相關的幾個函式:
pthread_mutex_init函式:初始化一個互斥鎖;
pthread_mutex_destroy函式:登出一個互斥鎖;
pthread_mutex_lock函式:加鎖,如果不成功,阻塞等待;
pthread_mutex_unlock函式:解鎖;
pthread_mutex_trylock函式:測試加鎖,如果不成功就立即返回,錯誤碼為EBUSY;
至於這些函式的用法,在這裡不多講了。windows下還有一個可以用來保護資料的方法,也是執行緒同步的方式
就是臨界區了。臨界區和互斥類似。它們之間的區別是,臨界區速度快,但是它只能用來同步同一個程序內的多個執行緒。臨界區的獲取和釋放函式如下:
EnterCriticalSection() 進入臨界區; LeaveCriticalSection()離開臨界區。
(2)採用訊息機制進行多執行緒通訊和同步,windows下面的的訊息機制的函式用的多的就是postmessage了。
(3)windows下的另外一種執行緒通訊方法就是事件和訊號量了。同樣針對開始舉得例子,2個執行緒同步,他們之間傳遞資訊,可以採用事件 (Event)或訊號量(Semaphore),比如第一個執行緒完成生產的資料後,就必須告訴第2個執行緒,他已經把資料準備好了,你可以來取走了。第2個 執行緒就把資料取走。這裡可以採用訊息機制,當第一個執行緒準備好資料後,就直接postmessage給第2個執行緒,按理說採用 postmessage一個執行緒就可以搞定這個問題了。
對於linux,也有類似的方法,即條件變數,這裡windows和linux有所不同。
對於windows,採用事件和訊號量同步時候,都會使用waitforsingleobject()進行等待,這個函式的第一個引數是一個控制代碼,在這裡可以是Event控制代碼,或Semaphore控制代碼,第2個引數就是等待的延遲,最終等多久,單位是ms,如果這個引數為INFINITE,那麼就是無限等待了。釋放訊號量的函式為ReleaseSemaphore();釋放事件的函式為SetEvent。使用這些東西都要初始化。
對於linux作業系統,是採用條件變數來實現類似的功能的。Linux的條件變數一般都是和互斥鎖一起使用的,主要的函式有:
pthread_mutex_lock ,
pthread_mutex_unlock,
pthread_cond_init
pthread_cond_signal
pthread_cond_wait
pthread_cond_timewait
比較:
(1) Pthread_cleanup_push,Pthread_cleanup_pop:
這一對函式push和pop的作用是當出現異常退出時,做一些清除操作,即當在push和pop函式之間異常退出,包括呼叫 pthread_exit退出,都會執行push裡面的清除函式,如果有多個push,注意是是棧,先執行後面的那個函式,在執行前面的函式,但是注意當 在這2個函式之間通過return 退出的話,執不執行push後的函式就看pop函式中的引數是不是為0了。還有當沒有異常退出時,等同於在這裡面return退出的情況,即:當pop函 數引數不為0時,執行清除操作,當pop函式引數為0時,不執行push函式中的清除函式。
(2)linux的pthread_cond_signal和SetEvent的不同點
Pthread_cond_singal釋放訊號後,當沒有Pthread_cond_wait,訊號馬上覆位了,這點和SetEvent不同,SetEvent是不會復位的。詳解如下:
條件變數的置位和復位有2種常用模型:第一種模型是當條件變數置位時(signaled)以後,如果當前沒有執行緒在等待,其狀態會保持為置位 (signaled),直到有等待的執行緒進入被觸發,其狀態才會變為unsignaled,這種模型以採用Windows平臺上的Auto-set Event 為代表。
第2種模型則是Linux平臺的pthread所採用的模型,當條件變數置位(signaled)以後,即使當前沒有任何執行緒在等待,其狀態也會恢復為復位(unsignaled)狀態。
條件變數在Linux平臺上的這種模型很難說好壞,在實際應用中,可以對程式碼稍加改進就可以避免這種差異的發生。由於這種差異只會發生在觸發沒有被執行緒等待在條件變數的時刻,因此只需要掌握好觸發的時機即可。最簡單的做法是增加一個計數器記錄等待執行緒的個數,在決定觸發條件變數前檢查該變數。
示例
使用 pthread_cond_wait() 和 pthread_cond_signal()
pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
unsigned count;
decrement_count()
{
pthread_mutex_lock(&count_lock);
while (count == 0)
pthread_cond_wait(&count_nonzero, &count_lock);
count = count - 1;
pthread_mutex_unlock(&count_lock);
}
increment_count()
{
pthread_mutex_lock(&count_lock);
if (count == 0)
pthread_cond_signal(&count_nonzero);
count = count + 1;
pthread_mutex_unlock(&count_lock);
}
(3) 注意Pthread_cond_wait條件返回時互斥鎖的解鎖問題
extern int pthread_cond_wait __P ((pthread_cond_t *__cond,pthread_mutex_t *__mutex));
呼叫這個函式時,執行緒解開mutex指向的鎖並被條件變數cond阻塞。執行緒可以被函式pthread_cond_signal和函式 pthread_cond_broadcast喚醒執行緒被喚醒後,它將重新檢查判斷條件是否滿足,如果還不滿足,一般說來執行緒應該仍阻塞在這裡,被等待被 下一次喚醒。如果在多執行緒中採用pthread_cond_wait來等待時,會首先釋放互斥鎖,當等待的訊號到來時,再次獲得互斥鎖,因此在之後要注意手動解鎖。舉例如下:
#include
#include
#include
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; /*初始化互斥鎖*/
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //初始化條件變數
void *thread1(void *);
void *thread2(void *);
int i=1;
int main(void)
{
pthread_t t_a;
pthread_t t_b;
pthread_create(&t_a,NULL,thread1,(void *)NULL);/*建立程序t_a*/
pthread_create(&t_b,NULL,thread2,(void *)NULL); /*建立程序t_b*/
pthread_join(t_b, NULL);/*等待程序t_b結束*/
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
exit(0);
}
void *thread1(void *junk)
{
for(i=1;i<=9;i++)
{
printf("IN one\n");
pthread_mutex_lock(&mutex);//
if(i%3==0)
pthread_cond_signal(&cond);/*,傳送訊號,通知t_b程序*/
else
printf("thead1:%d\n",i);
pthread_mutex_unlock(&mutex);//*解鎖互斥量*/
printf("Up Mutex\n");
sleep(3);
}
}
void *thread2(void *junk)
{
while(i<9)
{
printf("IN two \n");
pthread_mutex_lock(&mutex);
if(i%3!=0)
pthread_cond_wait(&cond,&mutex);/*等待*/
printf("thread2:%d\n",i);
pthread_mutex_unlock(&mutex);
printf("Down Mutex\n");
sleep(3);
}
}
輸出如下:
IN one
thead1:1
Up Mutex
IN two
IN one
thead1:2
Up Mutex
IN one
thread2:3
Down Mutex
Up Mutex
IN one
thead1:4
Up Mutex
IN two
IN one
thead1:5
Up Mutex
IN one
Up Mutex
thread2:6
Down Mutex
IN two
thread2:6
Down Mutex
IN one
thead1:7
Up Mutex
IN one
thead1:8
Up Mutex
IN two
IN one
Up Mutex
thread2:9
Down Mutex
注意藍色的地方,有2個thread2:6,其實當這個程式多執行幾次,i=3和i=6時有可能多列印幾個,這裡就是競爭鎖造成的了。
(4)另外要注意的Pthread_cond_timedwait等待的是絕對時間,這個和WaitForSingleObject是不同的,Pthread_cond_timedwait在網上也有討論。如下:
thread_a :
pthread_mutex_lock(&mutex);
//do something
pthread_mutex_unlock(&mutex)
thread_b:
pthread_mutex_lock(&mutex);
//do something
pthread_cond_timedwait(&cond, &mutex, &tm);
pthread_mutex_unlock(&mutex)
有如上兩個執行緒thread_a, thread_b,現在如果a已經進入了臨界區,而b同時超時了,那麼b會從pthread_cond_timedwait返回嗎?如果能返回,那豈不是 a,b都在臨界區?如果不能返回,那pthread_cond_timedwait的定時豈不是就不準了?
大家討論有價值的2點如下:
(1) pthread_cond_timedwait (pthread_cond_t *cv, pthread_mutex_t *external_mutex, const struct timespec *abstime) -- This function is a time-based variant of pthread_cond_wait. It waits up to abstime amount of time for cv to be notified. If abstime elapses before cv is notified, the function returns back to the caller with an ETIME result, signifying that a timeout has occurred. Even in the case of timeouts, the external_mutex will be locked when pthread_cond_timedwait returns.
(2) 2.1 pthread_cond_timedwait行為和pthread_cond_wait一樣,在返回的時候都要再次lock mutex.
2 .2pthread_cond_timedwait所謂的如果沒有等到條件變數,超時就返回,並不確切。
如果pthread_cond_timedwait超時到了,但是這個時候不能lock臨界區,pthread_cond_timedwait並不會立即 返回,但是在pthread_cond_timedwait返回的時候,它仍在臨界區中,且此時返回值為ETIMEDOUT。
關於pthread_cond_timedwait超時返回的問題,我也認同觀點2。
附錄:
int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict_attr,void*(*start_rtn)(void*),void *restrict arg);
返回值:若成功則返回0,否則返回出錯編號
返回成功時,由tidp指向的記憶體單元被設定為新建立執行緒的執行緒ID。attr引數用於制定各種不同的執行緒屬性。新建立的執行緒從 start_rtn函式的地址開始執行,該函式只有一個無指標引數arg,如果需要向start_rtn函式傳遞的引數不止一個,那麼需要把這些引數放到 一個結構中,然後把這個結構的地址作為arg的引數傳入。
linux下用C開發多執行緒程式,Linux系統下的多執行緒遵循POSIX執行緒介面,稱為pthread。
由 restrict 修飾的指標是最初唯一對指標所指向的物件進行存取的方法,僅當第二個指標基於第一個時,才能對物件進行存取。對物件的存取都限定於基於由 restrict 修飾的指標表示式中。 由 restrict 修飾的指標主要用於函式形參,或指向由 malloc() 分配的記憶體空間。restrict 資料型別不改變程式的語義。 編譯器能通過作出 restrict 修飾的指標是存取物件的唯一方法的假設,更好地優化某些型別的例程。
第一個引數為指向執行緒識別符號的指標。
第二個引數用來設定執行緒屬性。
第三個引數是執行緒執行函式的起始地址。
第四個引數是執行函式的引數。
因為pthread不是linux系統的庫,所以在編譯時注意加上-lpthread引數,以呼叫靜態連結庫。
終止執行緒:
如果在程序中任何一個執行緒中呼叫exit或_exit,那麼整個進行會終止,執行緒正常的退出方式有:
(1) 執行緒從啟動例程中返回(return)
(2) 執行緒可以被另一個程序終止(kill);
(3) 執行緒自己呼叫pthread_exit函式
#include
pthread_exit
執行緒等待:
int pthread_join(pthread_t tid,void **rval_ptr)
函式pthread_join用來等待一個執行緒的結束。函式原型為:
extern int pthread_join __P (pthread_t __th, void **__thread_return);
第一個引數為被等待的執行緒識別符號,第二個引數為一個使用者定義的指標,它可以用來儲存被等待執行緒的返回值。這個函式是一個執行緒阻塞的函式,呼叫它的函式將一直等待到被等待的執行緒結束為止,當函式返回時,被等待執行緒的資源被收回。