1 引言 線程(thread)技術早在60年代就被提出,但真正應用多線程到操作系統中去,是在80年代中期,solaris是這方面的佼佼者。傳統的Unix也支持線程的概念,但是在一個進程(process)中只允許有一個線程,這樣多線程就意味著多進程。現在,多線程技術已經被許多操作系統所支持,包括Windows/NT,當然,也包括Linux。 2 簡單的多線程編程 Linux系統下的多線程遵循POSIX線程接口,稱為pthread。編寫Linux下的多線程程序,需要使用頭文件pthread.h,連接時需要使用庫libpthread.a。順便說一下,Linux下pthread的實現是通過系統調用clone()來實現的。clone()是Linux所特有的系統調用,它的使用方式類似fork,關于clone()的詳細情況,有興趣的讀者可以去查看有關文檔說明。下面我們展示一個最簡單的多線程程序example1.c。 /* example.c*/ #include #include void thread(void) { int i; for(i=0;i<3;i++) printf("This is a pthread./n"); } int main(void) { pthread_t id; int i,ret; ret=pthread_create(&id,NULL,(void *) thread,NULL); if(ret!=0) { printf ("Create pthread error!/n"); exit (1); } for(i=0;i<3;i++) printf("This is the main process./n"); pthread_join(id,NULL); return (0); } 我們編譯此程序: gcc example1.c -lpthread -o example1
再次運行,我們可能得到如下結果:
前后兩次結果不一樣,這是兩個線程爭奪CPU資源的結果。上面的示例中,我們使用到了兩個函數, pthread_create和pthread_join,并聲明了一個pthread_t型的變量。 3 修改線程的屬性 在上一節的例子里,我們用pthread_create函數創建了一個線程,在這個線程中,我們使用了默認參數,即將該函數的第二個參數設為NULL。的確,對大多數程序來說,使用默認屬性就夠了,但我們還是有必要來了解一下線程的有關屬性。 #include pthread_attr_t attr; pthread_t tid; /*初始化屬性值,均設為默認值*/ pthread_attr_init(&attr); pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); pthread_create(&tid, &attr, (void *) my_function, NULL); 線程的分離狀態決定一個線程以什么樣的方式來終止自己。在上面的例子中,我們采用了線程的默認屬性,即為非分離狀態,這種情況下,原有的線程等待創建的線程結束。只有當pthread_join()函數返回時,創建的線程才算終止,才能釋放自己占用的系統資源。而分離線程不是這樣子的,它沒有被其他的線程所等待,自己運行結束了,線程也就終止了,馬上釋放系統資源。程序員應該根據自己的需要,選擇適當的分離狀態。設置線程分離狀態的函數為pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二個參數可選為PTHREAD_CREATE_DETACHED(分離線程)和 PTHREAD _CREATE_JOINABLE(非分離線程)。這里要注意的一點是,如果設置一個線程為分離線程,而這個線程運行又非常快,它很可能在pthread_create函數返回之前就終止了,它終止以后就可能將線程號和系統資源移交給其他的線程使用,這樣調用pthread_create的線程就得到了錯誤的線程號。要避免這種情況可以采取一定的同步措施,最簡單的方法之一是可以在被創建的線程里調用pthread_cond_timewait函數,讓這個線程等待一會兒,留出足夠的時間讓函數pthread_create返回。設置一段等待時間,是在多線程編程里常用的方法。但是注意不要使用諸如wait()之類的函數,它們是使整個進程睡眠,并不能解決線程同步的問題。 另外一個可能常用的屬性是線程的優先級,它存放在結構sched_param中。用函數pthread_attr_getschedparam和函數pthread_attr_setschedparam進行存放,一般說來,我們總是先取優先級,對取得的值修改后再存放回去。下面即是一段簡單的例子。 /* example.c*/ #include #include void thread(void) { int i; for(i=0;i<3;i++) printf("This is a pthread./n"); } int main(void) { pthread_t id; int i,ret; ret=pthread_create(&id,NULL,(void *) thread,NULL); if(ret!=0) { printf ("Create pthread error!/n"); exit (1); } for(i=0;i<3;i++) printf("This is the main process./n"); pthread_join(id,NULL); return (0); } 4 線程的數據處理 和進程相比,線程的最大優點之一是數據的共享性,各個進程共享父進程處沿襲的數據段,可以方便的獲得、修改數據。但這也給多線程編程帶來了許多問題。我們必須當心有多個不同的進程訪問相同的變量。許多函數是不可重入的,即同時不能運行一個函數的多個拷貝(除非使用不同的數據段)。在函數中聲明的靜態變量常常帶來問題,函數的返回值也會有問題。因為如果返回的是函數內部靜態聲明的空間的地址,則在一個線程調用該函數得到地址后使用該地址指向的數據時,別的線程可能調用此函數并修改了這一段數據。在進程中共享的變量必須用關鍵字volatile來定義,這是為了防止編譯器在優化時(如gcc中使用-OX參數)改變它們的使用方式。為了保護變量,我們必須使用信號量、互斥等方法來保證我們對變量的正確使用。下面,我們就逐步介紹處理線程數據時的有關知識。 4.1 線程數據 在單線程的程序里,有兩種基本的數據:全局變量和局部變量。但在多線程程序里,還有第三種數據類型:線程數據(TSD: Thread-Specific Data)。它和全局變量很象,在線程內部,各個函數可以象使用全局變量一樣調用它,但它對線程外部的其它線程是不可見的。這種數據的必要性是顯而易見的。例如我們常見的變量errno,它返回標準的出錯信息。它顯然不能是一個局部變量,幾乎每個函數都應該可以調用它;但它又不能是一個全局變量,否則在A線程里輸出的很可能是B線程的出錯信息。要實現諸如此類的變量,我們就必須使用線程數據。我們為每個線程數據創建一個鍵,它和這個鍵相關聯,在各個線程里,都使用這個鍵來指代線程數據,但在不同的線程里,這個鍵代表的數據是不同的,在同一個線程里,它代表同樣的數據內容。 /* example.c*/ #include #include void thread(void) { int i; for(i=0;i<3;i++) printf("This is a pthread./n"); } int main(void) { pthread_t id; int i,ret; ret=pthread_create(&id,NULL,(void *) thread,NULL); if(ret!=0) { printf ("Create pthread error!/n"); exit (1); } for(i=0;i<3;i++) printf("This is the main process./n"); pthread_join(id,NULL); return (0); } 這樣,在不同的線程中調用函數createMyWin,都可以得到在線程內部均可見的窗口變量,這個變量通過函數pthread_getspecific得到。在上面的例子中,我們已經使用了函數pthread_setspecific來將線程數據和一個鍵綁定在一起。這兩個函數的原型如下: extern int pthread_setspecific __P ((pthread_key_t __key,__const void *__pointer)); 4.2 互斥鎖 互斥鎖用來保證一段時間內只有一個線程在執行一段代碼。必要性顯而易見:假設各個線程向同一個文件順序寫入數據,最后得到的結果一定是災難性的。 void reader_function ( void ); void writer_function ( void ); char buffer; int buffer_has_item=0; pthread_mutex_t mutex; struct timespec delay; void main ( void ){ pthread_t reader; /* 定義延遲時間*/ delay.tv_sec = 2; delay.tv_nec = 0; /* 用默認屬性初始化一個互斥鎖對象*/ pthread_mutex_init (&mutex,NULL); pthread_create(&reader, pthread_attr_default, (void *)&reader_function), NULL); writer_function( ); } void writer_function (void){ while(1){ /* 鎖定互斥鎖*/ pthread_mutex_lock (&mutex); if (buffer_has_item==0){ buffer=make_new_item( ); buffer_has_item=1; } /* 打開互斥鎖*/ pthread_mutex_unlock(&mutex); pthread_delay_np(&delay); } } void reader_function(void){ while(1){ pthread_mutex_lock(&mutex); if(buffer_has_item==1){ consume_item(buffer); buffer_has_item=0; } pthread_mutex_unlock(&mutex); pthread_delay_np(&delay); } } 這里聲明了互斥鎖變量mutex,結構pthread_mutex_t為不公開的數據類型,其中包含一個系統分配的屬性對象。函數pthread_mutex_init用來生成一個互斥鎖。NULL參數表明使用默認屬性。如果需要聲明特定屬性的互斥鎖,須調用函數pthread_mutexattr_init。函數pthread_mutexattr_setpshared和函數pthread_mutexattr_settype用來設置互斥鎖屬性。前一個函數設置屬性pshared,它有兩個取值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用來不同進程中的線程同步,后者用于同步本進程的不同線程。在上面的例子中,我們使用的是默認屬性PTHREAD_PROCESS_ PRIVATE。后者用來設置互斥鎖類型,可選的類型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT。它們分別定義了不同的上所、解鎖機制,一般情況下,選用最后一個默認屬性。 pthread_mutex_lock聲明開始用互斥鎖上鎖,此后的代碼直至調用pthread_mutex_unlock為止,均被上鎖,即同一時間只能被一個線程調用執行。當一個線程執行到pthread_mutex_lock處時,如果該鎖此時被另一個線程使用,那此線程被阻塞,即程序將等待到另一個線程釋放此互斥鎖。在上面的例子中,我們使用了pthread_delay_np函數,讓線程睡眠一段時間,就是為了防止一個線程始終占據此函數。 4.3 條件變量 前一節中我們講述了如何使用互斥鎖來實現線程間數據的共享和通信,互斥鎖一個明顯的缺點是它只有兩種狀態:鎖定和非鎖定。而條件變量通過允許線程阻塞和等待另一個線程發送信號的方法彌補了互斥鎖的不足,它常和互斥鎖一起使用。使用時,條件變量被用來阻塞一個線程,當條件不滿足時,線程往往解開相應的互斥鎖并等待條件發生變化。一旦其它的某個線程改變了條件變量,它將通知相應的條件變量喚醒一個或多個正被此條件變量阻塞的線程。這些線程將重新鎖定互斥鎖并重新測試條件是否滿足。一般說來,條件變量被用來進行線承間的同步。 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); } count值為0時,decrement函數在pthread_cond_wait處被阻塞,并打開互斥鎖count_lock。此時,當調用到函數increment_count時,pthread_cond_signal()函數改變條件變量,告知decrement_count()停止阻塞。讀者可以試著讓兩個線程分別運行這兩個函數,看看會出現什么樣的結果。 函數pthread_cond_broadcast(pthread_cond_t *cond)用來喚醒所有被阻塞在條件變量cond上的線程。這些線程被喚醒后將再次競爭相應的互斥鎖,所以必須小心使用這個函數。 4.4 信號量 信號量本質上是一個非負的整數計數器,它被用來控制對公共資源的訪問。當公共資源增加時,調用函數sem_post()增加信號量。只有當信號量值大于0時,才能使用公共資源,使用后,函數sem_wait()減少信號量。函數sem_trywait()和函數pthread_ mutex_trylock()起同樣的作用,它是函數sem_wait()的非阻塞版本。下面我們逐個介紹和信號量有關的一些函數,它們都在頭文件/usr/include/semaphore.h中定義。 /* File sem.c */ #include #include #include #define MAXSTACK 100 int stack[MAXSTACK][2]; int size=0; sem_t sem; /* 從文件1.dat讀取數據,每讀一次,信號量加一*/ void ReadData1(void){ FILE *fp=fopen("1.dat","r"); while(!feof(fp)){ fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]); sem_post(&sem); ++size; } fclose(fp); } /*從文件2.dat讀取數據*/ void ReadData2(void){ FILE *fp=fopen("2.dat","r"); while(!feof(fp)){ fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]); sem_post(&sem); ++size; } fclose(fp); } /*阻塞等待緩沖區有數據,讀取數據后,釋放空間,繼續等待*/ void HandleData1(void){ while(1){ sem_wait(&sem); printf("Plus:%d+%d=%d/n",stack[size][0],stack[size][1], stack[size][0]+stack[size][1]); --size; } } void HandleData2(void){ while(1){ sem_wait(&sem); printf("Multiply:%d*%d=%d/n",stack[size][0],stack[size][1], stack[size][0]*stack[size][1]); --size; } } int main(void){ pthread_t t1,t2,t3,t4; sem_init(&sem,0,0); pthread_create(&t1,NULL,(void *)HandleData1,NULL); pthread_create(&t2,NULL,(void *)HandleData2,NULL); pthread_create(&t3,NULL,(void *)ReadData1,NULL); pthread_create(&t4,NULL,(void *)ReadData2,NULL); /* 防止程序過早退出,讓它在此無限期等待*/ pthread_join(t1,NULL); } 在Linux下,我們用命令gcc -lpthread sem.c -o sem生成可執行文件sem。 我們事先編輯好數據文件1.dat和2.dat,假設它們的內容分別為1 2 3 4 5 6 7 8 9 10和 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 ,我們運行sem,得到如下的結果: Multiply:-1*-2=2 Plus:-1+-2=-3 Multiply:9*10=90 Plus:-9+-10=-19 Multiply:-7*-8=56 Plus:-5+-6=-11 Multiply:-3*-4=12 Plus:9+10=19 Plus:7+8=15 Plus:5+6=11 從中我們可以看出各個線程間的競爭關系。而數值并未按我們原先的順序顯示出來這是由于size這個數值被各個線程任意修改的緣故。這也往往是多線程編程要注意的問題。 5 小結 多線程編程是一個很有意思也很有用的技術,使用多線程技術的網絡螞蟻是目前最常用的下載工具之一,使用多線程技術的grep比單線程的grep要快上幾倍,類似的例子還有很多。希望大家能用多線程技術寫出高效實用的好程序來。 |
免責聲明:本站部分文章和圖片均來自用戶投稿和網絡收集,旨在傳播知識,文章和圖片版權歸原作者及原出處所有,僅供學習與參考,請勿用于商業用途,如果損害了您的權利,請聯系我們及時修正或刪除。謝謝!
始終以前瞻性的眼光聚焦站長、創業、互聯網等領域,為您提供最新最全的互聯網資訊,幫助站長轉型升級,為互聯網創業者提供更加優質的創業信息和品牌營銷服務,與站長一起進步!讓互聯網創業者不再孤獨!
掃一掃,關注站長網微信
當我們在共享網絡訪問的時候,可能會遇到提示指定的網絡名不再可用的問題,這可能是由于我們的共享網絡出現了錯誤,也可能是被共享的對象所拒絕了。指定的網絡名 ......
文/曹楊 原標題:誰還看電視? 爸爸戴一副老花鏡,媽媽戴一副近視鏡,一人坐在沙發,一人躺在床上,各自刷著自己關注的博主更新的短視頻。電視也許開著,但只是背景。 這樣的畫面,幾乎成了洛奇家的常 ...
圖片來源于簡書 文/郭開森 楊帆 陸玖財經準備開新欄目了,每周一創始人郭開森和楊帆合體郭德帆,對行業進行一些觀察和評論,第一篇我們仍是打算寫社區團購,這是當下最火的話題。 來過陸玖財經做客的朋友們...
1、首先進入到“百度”軟件中, 2、然后在其中輸入“百度識圖”, 3、之后點擊圖中的“開始使用”按鈕, 4、緊接著點擊右下角的“相冊”功能, 5、在相冊下 ......
一、軟件沖突1、首先確認是否是應用程序沖突導致的。2、查看是否只有特定幾個游戲或應用會導致該問題。3、如果是應用沖突,那么只要卸載這些app就可以解決了。二 ......
電腦端:1、大家可以點擊右邊鏈接進入網頁版的百度網盤,進入之后點擊“去登錄”。https://pan.baidu.com/2、之后正確的輸入賬號密碼進行登錄就好啦。手機端:1 ......
在填寫一些項目申請書中,總是免不了要選擇一些數字,但是在方框中如何插入數字,該怎么辦呢?那么下面就由學習啦小編給大家分享下word在方框里輸入數字的技巧, ......
8月15日消息 上周,有媒體報道前身為百度圖片的“榴蓮”APP含有大量不雅視頻內容被用戶舉報。對此,百度圖片官方進行了回應,百度圖片表示已經對報道中所涉及的“生吃旋風哥”等爭議內容進行了下線處理。 此外,百度...
一、N100對比intel i3 1、N100的跑分達到了147210分,這個數據可以達到i3的七代級別。 2、在跑分上也是超越了大部分的I3七代CPU,不過比I3八代要弱勢一些。 3 ......
WPS Office手機版怎么加橫線?很多用戶還不知道WPS Office手機版怎么加橫線,WPS Office手機版怎么加橫線,WPS Office手機版怎么打橫線,WPS Office手機版怎么弄 ......
迅雷前綴是什么 答:迅雷前綴是(magnet:?xt=urn:btih:)括號里的就是了。 我們只要在這段文字之后輸入后續的內容,就可以創建下載鏈接了。 1、磁力鏈接不基于文 ......
一、內容特權。 1、半價點播。 許多站內視頻都需要付費觀看,而大會員用戶可以直接半價享受; 購買成功后的48h內無限次觀看。有部分的內容是只限在中國大陸內觀 ......
1、首先打開小米運動的“實驗室功能”。 2、接著點擊“門卡模擬”。 3、然后點擊“我知道了”。 4、最后貼近就可以刷卡成功了。...
答:華為P系列: 華為p40,華為p40plus,華為p50,華為p50e,華為p60 華為mate系列: 華為mate40,華為mate50,華為mate50e,華為mate60 華為nova系列: 華為n ......
近期有用戶反映,電腦在更新Windows 11 Insider Preview 25252.1000后,出現了應用和已壓縮的文件點擊毫無反應,拖拽都不行,只能從開始菜單打開的情況,這是怎 ......
可見單元格就是不包括隱藏或者篩選篩選后隱藏起來的單元格區域。方法:篩選或隱藏數據,復制需要粘貼的值,在目標單元格區域左上角的第一個單元格處右擊,選擇【 ......
答:驍龍8+更好。 驍龍7+gen2實際上就是驍龍8+的低配版本。 在一些其他的核心架構方面都是保持一致的,比如說CPU的架構、GPU的架構等等。 驍龍7+和驍龍8+具體 ......
文/黎明 一場針對中國互聯網巨頭的反壟斷風暴正在醞釀,而且這次動真格了。 11月10日,國家市場監管總局發布《關于平臺經濟領域的反壟斷指南(征求意見稿)》,要加大對互聯網巨頭涉嫌壟斷的調查和監管。 ...
win11系統如何釋放掉系統默認保留的存儲空間?一般情況下,Windows會保留一些存儲空間,以便設備獲得良好性能和成功更新。但是當出現系統盤儲存空間不足時,我們會將幾個G的保留空間釋放出來,以解燃眉之急。本期教...
文件被win10系統誤報病毒自動刪除了如何進行恢復?有用戶下載了某些破解軟件卻被Win10系統誤認為是病毒文件而自動刪除,當然系統自帶殺毒軟件其實挺不錯的,就是有時候會誤報,大家遇到這種情況的時候就希望把誤刪的...
win11系統快速跳過聯網創建本地管理賬戶3種方法?現在市面上銷售的品牌筆記本和臺式機基本上都預裝Windows11家庭中文版正版操作系統,聯網后系統會自動激活。當用戶拿到新機器后還需要按照cortana(小娜)的提示一步...
羅技g304dpi燈顏色代表什么:1、藍色:這種情況是正常工作的顯示,如果說是常亮或者閃爍,那都沒有問題這是在正常工作呢。2、紅色:如果說是紅燈閃爍的話那就是 ......
答:在3DMark壓力測試當中,顯卡需要超高97%才能夠算合格,證明顯卡的穩定性是過關的。 1、一般的默認情況下在2500~3000分就算很正常的了。 2、分數越高說明顯卡 ......
1、先打開機頂盒進入主界面,并且使用遙控器打開設置。 2、然后選擇“賬號與安全”,并且進入。 3、最后往下面翻就可以看到“ADB調試”的選項,直接開啟就行了 ......
相信有非常多使用過筆記本的用戶都聽說過獨顯直連這個詞,但很多用戶并不了解獨顯直連是什么,又有什么用處,那么下面就和小編一起來看看什么是獨顯直連和開啟這 ......
win11系統開機總是自動登錄OneDrive如何關閉?win11系統開機的時候,會自動啟動OneDrive,不想要啟動,該怎么操作呢?下面我們就來看看詳細的教程。 在OneDrive界面點小齒輪按鈕,下拉菜單中點【設置】。 單擊【...
背景 有時候我們需要獲取文件的創建時間。 例如: 我在研究 《xtrabackup 原理圖》的時候,想通過觀察確認 xtrabackup_log 是最早創建 并且是 最晚保存的 ......
答:中高端水平 i513500hx在處理器當中是處于一個中高端的水平。 i513500hx是第十一代酷睿處理器系列的一員,基礎頻率為2.4GHz,表現十分的不錯。 i513500hx介 ......
1、首先確認手機型號是否支持無線充電功能,(可以在品牌官網找到手機信息查看)2、查看充電板的指示燈是否亮起。指示燈不亮檢查充電器、數據線、電源之間連接是 ......