很早之前就想寫寫linux下多線程編程和windows下的多線程編程了,可是每當寫時又不知道從哪一個地方寫起,怎樣把本身知道的東西都寫出來,下面我就談談linux多線程及線程同步,並將它和windows的多線程進行比較,看看他們之間有什麼相同點和不一樣的地方。linux
其實最開始我是搞windows下編程的,包括windows編程,windows 驅動,包括usb驅動,ndis驅動,pci驅動,1394驅動等等,同時也一條龍服務,作windows下的應用程序開發,後面慢慢的我又對linux 開發產生比較深的興趣和愛好,就轉到搞linux開發了。在接下來的我還會寫一些博客,主要是寫linux編程和windows編程的區別吧,如今想寫的 是linux下usb驅動和windows下usb驅動開發的區別,這些都是後話,等我將linux多線程和windows多線程講解完後,我再寫一篇 usb驅動,談談windows 和linux usb驅動的東東。好了,言歸正傳。開始將多線程了。編程
首先咱們講講爲何要採用多線程編程,其實並非全部的程序都必須採用多線程,有些時候採用多線程,性能尚未單線程好。因此咱們要搞清楚,何時採用多線程。採用多線程的好處以下:windows
(1)由於多線程彼此之間採用相同的地址空間,共享大部分的數據,這樣和多進程相比,代價比較節儉,由於多進程的話,啓動新的進程必須分配給它獨立的地址空間,這樣須要數據表來維護代碼段,數據段和堆棧段等等。多線程
(2)多線程和多進程相比,一個明顯的優勢就是線程之間的通訊了,對不一樣進程來講,它們具備獨立的數據空間,要進行數據的傳遞只能經過通訊的方式進 行,這種方式不只費時,並且很不方便。可是對於多線程就不同了。他們之間能夠直接共享數據,好比最簡單的方式就是共享全局變量。可是共享所有變量也要注 意哦,呵呵,必須注意同步,否則後果你知道的。呵呵。wordpress
(3)在多cpu的狀況下,不一樣的線程能夠運行不一樣的cpu下,這樣就徹底並行了。函數
反正我以爲在這種狀況下,採用多線程比較理想。好比說你要作一個任務分2個步驟,你爲提升工做效率,你能夠多線程技術,開闢2個線程,第一個線程就 作第一步的工做,第2個線程就作第2步的工做。可是你這個時候要注意同步了。由於只有第一步作完才能作第2步的工做。這時,咱們能夠採用同步技術進行線程 之間的通訊。post
針對這種狀況,咱們首先講講多線程之間的通訊,在windows平臺下,多線程之間通訊採用的方法主要有:性能
(1)共享全局變量,這種方法是最容易想到的,呵呵,那就首先講講吧,好比說吧,上面的問題,第一步要向第2步傳遞收據,咱們能夠之間共享全局變 量,讓兩個線程之間傳遞數據,這時主要考慮的就是同步了,由於你後面的線程在對數據進行操做的時候,你第一個線程又改變了數據的內容,你不一樣步保護,後果 很嚴重的。你也知道,這種狀況就是讀髒數據了。在這種狀況下,咱們最容易想到的同步方法就是設置一個bool flag了,好比說在第2個線程尚未用完數據前,第一個線程不能寫入。有時在2個線程所需的時間不相同的時候,怎樣達到最大效率的同步,就比較麻煩了。 我們能夠多開幾個緩衝區進行操做。就像生產者消費者同樣了。若是是2個線程一直在跑的,因爲時間不一致,緩衝區早晚會溢出的。在這種狀況下就要考慮了,是 不讓數據寫入仍是讓數據覆蓋掉老的數據,這時候就要具體問題具體分析了。就此打住,呵呵。就是用bool變量控制同步,linux 和windows是同樣的。測試
既然講道了這裏,就再講講其它同步的方法。一樣 針對上面的這個問題,共享全局變量同步問題。除了採用bool變量外,最容易想到的方法就是互斥量了。呵呵,也就是傳說中的加鎖了。windows下加鎖 和linux下加鎖是相似的。採用互斥量進行同步,要想進入那段代碼,就先必須得到互斥量。優化
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;
至於這些函數的用法,google上一搜,就出來了,呵呵,在這裏很少講了。windows下還有一個能夠用來保護數據的方法,也是線程同步的方式
就是臨界區了。臨界區和互斥相似。它們之間的區別是,臨界區速度快,可是它只能用來同步同一個進程內的多個線程。臨界區的獲取和釋放函數以下:
EnterCriticalSection() 進入臨界區; LeaveCriticalSection()離開臨界區。 對於多線程共享內存的東東就講到這裏了。
(2)採用消息機制進行多線程通訊和同步,windows下面的的消息機制的函數用的多的就是postmessage了。Linux下的消息機制,我用的較少,就不在這裏說了,若是誰熟悉的,也告訴我,呵呵。
(3)windows下的另一種線程通訊方法就是事件和信號量了。一樣針對我開始舉得例子,2個線程同步,他們之間傳遞信息,能夠採用事件 (Event)或信號量(Semaphore),好比第一個線程完成生產的數據後,就必須告訴第2個線程,他已經把數據準備好了,你能夠來取走了。第2個 線程就把數據取走。呵呵,這裏能夠採用消息機制,當第一個線程準備好數據後,就直接postmessage給第2個線程,按理說採用 postmessage一個線程就能夠搞定這個問題了。呵呵,不是重點,省略不講了。
對於linux,也有相似的方法,就是條件變量了,呵呵,這裏windows和linux就有不一樣了。要特別講講才行。
對於windows,採用事件和信號量同步時候,都會使用waitforsingleobject進行等待的,這個函數的第一個參數是一個句柄,在 這裏能夠是Event句柄,或Semaphore句柄,第2個參數就是等待的延遲,最終等多久,單位是ms,若是這個參數爲INFINITE,那麼就是無 限等待了。釋放信號量的函數爲ReleaseSemaphore();釋放事件的函數爲SetEvent。固然使用這些東西都要初始化的。這裏就不講了。 Msdn一搜,神馬都出來了,呵呵。神馬都是浮雲!
對於linux操做系統,是採用條件變量來實現相似的功能的。Linux的條件變量通常都是和互斥鎖一塊兒使用的,主要的函數有:
pthread_mutex_lock ,
pthread_mutex_unlock,
pthread_cond_init
pthread_cond_signal
pthread_cond_wait
pthread_cond_timewait
爲了和windows操做系統進行對比,我用如下表格進行比較:
對照以上表格,總結以下:
(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);
第一個參數爲被等待的線程標識符,第二個參數爲一個用戶定義的指針,它能夠用來存儲被等待線程的返回值。這個函數是一個線程阻塞的函數,調用它的函數將一直等待到被等待的線程結束爲止,當函數返回時,被等待線程的資源被收回。
對於windows線程的建立東西,就不列舉了,msdn上 一搜就出來了。呵呵。今天就講到這裏吧,但願是拋磚引玉,你們一塊兒探討,呵呵。部份內容我也是參考internet的,特此對原做者表示感謝!