Linux多執行緒程式設計小結
Linux多執行緒程式設計小結
前一段時間因為開題的事情一直耽擱了我搞Linux的進度,搞的我之前學的東西都遺忘了,很煩躁的說,現在抽個時間把之前所學的做個小節。文章內容主要總結於《Linux程式設計第3版》。
1.Linux程序與執行緒
Linux程序建立一個新執行緒時,執行緒將擁有自己的棧(因為執行緒有自己的區域性變數),但與它的建立者共享全域性變數、檔案描述符、訊號控制代碼和當前目錄狀態。
Linux通過fork建立子程序與建立執行緒之間是有區別的:fork創建出該程序的一份拷貝,這個新程序擁有自己的變數和自己的PID,它的時間排程是獨立的,它的執行幾乎完全獨立於父程序。
程序可以看成一個資源的基本單位,而執行緒是程式排程的基本單位,一個程序內部的執行緒之間共享程序獲得的時間片。
2._REENTRANT巨集
在一個多執行緒程式裡,預設情況下,只有一個errno變數供所有的執行緒共享。在一個執行緒準備獲取剛才的錯誤程式碼時,該變數很容易被另一個執行緒中的函式呼叫所改變。類似的問題還存在於fputs之類的函式中,這些函式通常用一個單獨的全域性性區域來快取輸出資料。
為解決這個問題,需要使用可重入的例程。可重入程式碼可以被多次呼叫而仍然工作正常。編寫的多執行緒程式,通過定義巨集_REENTRANT來告訴編譯器我們需要可重入功能,這個巨集的定義必須出現於程式中的任何#include語句之前。
_REENTRANT為我們做三件事情,並且做的非常優雅:
(1)它會對部分函式重新定義它們的可安全重入的版本,這些函式名字一般不會發生改變,只是會在函式名後面新增
(2)stdio.h中原來以巨集的形式實現的一些函式將變成可安全重入函式。
(3)在error.h中定義的變數error現在將成為一個函式呼叫,它能夠以一種安全的多執行緒方式來獲取真正的errno的值。
3.執行緒的基本函式
大多數pthread_XXX系列的函式在失敗時,並未遵循UNIX函式的慣例返回-1,這種情況在UNIX函式中屬於一少部分。如果呼叫成功,則返回值是0,如果失敗則返回錯誤程式碼。
1.執行緒建立:
#include <pthread.h>
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
引數說明:
thread:指向pthread_create型別的指標,用於引用新建立的執行緒。
attr:用於設定執行緒的屬性,一般不需要特殊的屬性,所以可以簡單地設定為NULL。
*(*start_routine)(void *):傳遞新執行緒所要執行的函式地址。
arg:新執行緒所要執行的函式的引數。
呼叫如果成功,則返回值是0,如果失敗則返回錯誤程式碼。
2.執行緒終止
#include <pthread.h>
void pthread_exit(void *retval);
引數說明:
retval:返回指標,指向執行緒向要返回的某個物件。
執行緒通過呼叫pthread_exit函式終止執行,並返回一個指向某物件的指標。注意:絕不能用它返回一個指向區域性變數的指標,因為執行緒呼叫該函式後,這個區域性變數就不存在了,這將引起嚴重的程式漏洞。
3.執行緒同步
#include <pthread.h>
int pthread_join(pthread_t th, void **thread_return);
引數說明:
th:將要等待的張璐,執行緒通過pthread_create返回的識別符號來指定。
thread_return:一個指標,指向另一個指標,而後者指向執行緒的返回值。
一個簡單的多執行緒Demo(thread1.c):
編譯這個程式時,需要定義巨集_REENTRANT:
gcc -D_REENTRANT thread1.c -o thread1 –lpthread
執行這個程式:
$ ./thread1輸出:
thread_function is running. Argument was Hello World
Waiting for thread to finish...
Thread joined, it returned Thank you for your CPU time!
Message is now Bye!
這個例子值得我們去花時間理解,因為它將作為幾個例子的基礎。
pthread_exit(void *retval)本身返回的就是指向某個物件的指標,因此,pthread_join(pthread_t th, void **thread_return);中的thread_return是二級指標,指向執行緒返回值的指標。
可以看到,我們建立的新執行緒修改的陣列message的值,而原先的執行緒也可以訪問該陣列。如果我們呼叫的是fork而不是pthread_create,就不會有這樣的效果了。原因是fork建立子程序之後,子程序會拷貝父程序,兩者分離,相互不干擾,而執行緒之間則是共享程序的相關資源。
4.執行緒的同時執行
接下來,我們來編寫一個程式,以驗證兩個執行緒的執行是同時進行的。當然,如果是在一個單處理器系統上,執行緒的同時執行就需要靠CPU線上程之間的快速切換來實現了。
我們的程式需要利用一個原理:即除了區域性變數外,所有其他的變數在一個程序中的所有執行緒之間是共享的。
在這個程式中,我們是在兩個執行緒之間使用輪詢技術,這種方式稱為忙等待,所以它的效率會很低。在本文的後續部分,我們將介紹一種更好的解決辦法。
下面的程式碼中,兩個執行緒會不斷的輪詢判斷flag的值是否滿足各自的要求。
編譯這個程式:
gcc -D_REENTRANT thread2.c -o thread2 –lpthread
執行這個程式:
$ ./thread2
121212121212121212
Waiting for thread to finish...
5.執行緒的同步
在上述示例中,我們採用輪詢的方式在兩個執行緒之間不停地切換是非常笨拙且沒有效率的實現方式,幸運的是,專門有一級設計好的函式為我們提供更好的控制執行緒執行和訪問程式碼臨界區的方法。
本小節將介紹兩個執行緒同步的基本方法:訊號量和互斥量。這兩種方法很相似,事實上,它們可以互相通過對方來實現。但在實際的應用中,對於一些情況,可能使用訊號量或互斥量中的一個更符合問題的語義,並且效果更好。
5.1用訊號量進行同步
1.訊號量建立
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
引數說明:
sem:訊號量物件。
pshared:控制訊號量的型別,0表示這個訊號量是當前程序的區域性訊號量,否則,這個訊號量就可以在多個程序之間共享。
value:訊號量的初始值。
2.訊號量控制
#include <semaphore.h>
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
sem_post的作用是以原子操作的方式給訊號量的值加1。
sem_wait的作用是以原子操作的方式給訊號量的值減1,但它會等到訊號量非0時才會開始減法操作。如果對值為0的訊號量呼叫sem_wait,這個函式就會等待,直到有執行緒增加了該訊號量的值使其不再為0。
3.訊號量銷燬
#include <semaphore.h>
int sem_destory(sem_t *sem);
這個函式的作用是,用完訊號量後對它進行清理,清理該訊號量所擁有的資源。如果你試圖清理的訊號量正被一些執行緒等待,就會收到一個錯誤。
與大多數Linux函式一樣,這些函式在成功時都返回0。
下面編碼實現輸入字串,統計每行的字元個數,以“end”結束輸入:
編譯這個程式:
gcc -D_REENTRANT thread2.c -o thread2 –lpthread
執行這個程式:
$ ./thread3
Input some text. Enter 'end' to finish
123
You input 3 characters
1234
You input 4 characters
12345
You input 5 characters
end
Waiting for thread to finish…
Thread join
通過使用訊號量,我們阻塞了統計字元個數的執行緒,這個程式似乎對快速的文字輸入和悠閒的暫停都很適用,比之前的輪詢解決方案效率上有了本質的提高。
5.2用互斥量進行執行緒同步
另一種用在多執行緒程式中同步訪問的方法是使用互斥量。它允許程式設計師鎖住某個物件,使得每次只能有一個執行緒訪問它。為了控制對關鍵程式碼的訪問,必須在進入這段程式碼之前鎖住一個互斥量,然後在完成操作之後解鎖它。
用於互斥量的基本函式和用於訊號量的函式非常相似:
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t, *mutexattr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destory(pthread_mutex_t *mutex);
與其他函式一樣,成功時返回0,失敗時將返回錯誤程式碼,但這些函式並不設定errno,所以必須對函式的返回程式碼進行檢查。互斥量的屬性設定這裡不討論,因此設定成NULL。
我們用互斥量來重寫剛才的程式碼如下:
編譯這個程式:
gcc -D_REENTRANT thread4.c -o thread4 –lpthread
執行這個程式:
$ ./thread4
Input some text. Enter 'end' to finish
123
You input 3 characters
1234
You input 4 characters
12345
You input 5 characters
end
You input 3 characters
Thread joined
6.執行緒的屬性
之前我們並未談及到執行緒的屬性,可以控制的執行緒屬性是非常多的,這裡面只列舉一些常用的。
如在前面的示例中,我們都使用的pthread_join同步執行緒,但其實有些情況下,我們並不需要。如:主執行緒為服務執行緒,而第二個執行緒為資料備份執行緒,備份工作完成之後,第二個執行緒可以直接終止了,它沒有必要再返回到主執行緒中。因此,我們可以建立一個“脫離執行緒”。
下面介紹幾個常用的函式:
(1)int pthread_attr_init (pthread_attr_t* attr);
功能:對執行緒屬性變數的初始化。
attr:執行緒屬性。
函式返回值:成功:0,失敗:-1
(2) int pthread_attr_setscope (pthread_attr_t* attr, int scope);
功能:設定執行緒繫結屬性。
attr:執行緒屬性。
scope:PTHREAD_SCOPE_SYSTEM(繫結);PTHREAD_SCOPE_PROCESS(非繫結)
函式返回值:成功:0,失敗:-1
(3) int pthread_attr_setdetachstate (pthread_attr_t* attr, int detachstate);
功能:設定執行緒分離屬性。
attr:執行緒屬性。
detachstate:PTHREAD_CREATE_DETACHED(分離);PTHREAD_CREATE_JOINABLE(非分離)
函式返回值:成功:0,失敗:-1
(4) int pthread_attr_setschedpolicy(pthread_attr_t* attr, int policy);
功能:設定建立執行緒的排程策略。
attr:執行緒屬性;
policy:執行緒排程策略:SCHED_FIFO、SCHED_RR和SCHED_OTHER。
函式返回值:成功:0,失敗:-1
(5) int pthread_attr_setschedparam (pthread_attr_t* attr, struct sched_param* param);
功能:設定執行緒優先順序。
attr:執行緒屬性。
param:執行緒優先順序。
函式返回值:成功:0,失敗:-1
(6) int pthread_attr_destroy (pthread_attr_t* attr);
功能:對執行緒屬性變數的銷燬。
attr:執行緒屬性。
函式返回值:成功:0,失敗:-1
(7)其他
int pthread_attr_setguardsize(pthread_attr_t* attr,size_t guardsize);//設定新建立執行緒棧的保護區大小。
int pthread_attr_setinheritsched(pthread_attr_t* attr, int inheritsched);//決定怎樣設定新建立執行緒的排程屬性。
int pthread_attr_setstack(pthread_attr_t* attr, void* stackader,size_t stacksize);//兩者共同決定了執行緒棧的基地址以及堆疊的最小尺寸(以位元組為單位)。
int pthread_attr_setstackaddr(pthread_attr_t* attr, void* stackader);//決定了新建立執行緒的棧的基地址。
int pthread_attr_setstacksize(pthread_attr_t* attr, size_t stacksize);//決定了新建立執行緒的棧的最小尺寸(以位元組為單位)。
例:建立優先順序為10的執行緒。
pthread_attr_t attr;
struct sched_param param;
pthread_attr_init(&attr);
pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); //繫結
pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); //分離
pthread_attr_setschedpolicy(&attr, SCHED_RR);
param.sched_priority = 10;
pthread_attr_setschedparam(&attr, ¶m);
pthread_create(xxx, &attr, xxx, xxx);
pthread_attr_destroy(&attr);
下面實現一個脫離執行緒的程式,建立一個執行緒,其屬性設定為脫離狀態。子執行緒結束時,要使用pthread_exit,原來的主執行緒不再等待與子執行緒重新合併。程式碼如下:
編譯這個程式:
gcc -D_REENTRANT thread5.c -o thread5 –lpthread
執行這個程式:
$ ./thread5
thread_function is running. Argument: hello world!
Waiting for thread to finished...
Waiting for thread to finished...
Waiting for thread to finished...
Waiting for thread to finished...
Second thread setting finished flag, and exiting now
Other thread finished!
通過設定執行緒的屬性,我們還可以控制執行緒的除錯,其方式與設定脫離狀態是一樣的。
7.取消一個執行緒
有時,我們想讓一個執行緒可以要求另一個執行緒終止,執行緒有方法做到這一點,與訊號處理一樣,執行緒可以在被要求終止時改變其行為。
先來看用於請求一個執行緒終止的函式:
#include <pthread.h>
int pthread_cancel(pthread_t thread);
這個函式簡單易懂,提供一個執行緒識別符號,我們就可以傳送請求來取消它。
執行緒可以用pthread_setcancelstate設定自己的取消狀態。
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
相關推薦
Linux多執行緒程式設計小結
Linux多執行緒程式設計小結 前一段時間因為開題的事情一直耽擱了我搞Linux的進度,搞的我之前學的東西都遺忘了,很煩躁的說,現在抽個時間把之前所學的做個小節。文章內容主要總結於《Linux程式設計第3版》。 1.Linux程序與執行緒 Linux程序建立一個新執行緒時
Linux多執行緒程式設計---執行緒間同步(互斥鎖、條件變數、訊號量和讀寫鎖)
本篇博文轉自http://zhangxiaoya.github.io/2015/05/15/multi-thread-of-c-program-language-on-linux/ Linux下提供了多種方式來處理執行緒同步,最常用的是互斥鎖、條件變數、訊號量和讀寫鎖。 下面是思維導
linux多執行緒程式設計,用 pthread_cond_timedwait 代替sleep
摘要:多執行緒程式設計中,執行緒A迴圈計算,然後sleep一會接著計算(目的是減少CPU利用率);存在的問題是,如果要關閉程式,通常選擇join執行緒A等待執行緒A退出,可是我們必須等到sleep函式返回,該執行緒A才能正常退出,這無疑減慢了程式退出的速度。當然,你可以terminate執行緒A,但
Linux多執行緒程式設計
執行緒概念 執行緒是指執行中的程式的排程單位。一個執行緒指的是程序中一個單一順序的控制流,也被稱為輕量級執行緒。它是系統獨立排程和分配的基本單位。同一程序中的多個執行緒將共享該系統中的全部系統資源,比如檔案描述符和訊號處理等。一個程序可以有很多執行緒,每
Linux多執行緒程式設計入門
1 執行緒基本知識 程序是資源管理的基本單元,而執行緒是系統排程的基本單元,執行緒是作業系統能夠進行排程運算的最小單位,它被包含在程序之中,是程序中的實際運作單位。一條執行緒指的是程序中一個單一順序的控制流,一個程序中可以併發多個執行緒,每條執行緒並行執行不同
Linux 多執行緒程式設計(一)
Linux 多執行緒程式設計 執行緒(Thread)已被許多作業系統所支援,包括Windows/NT ,Linux 以前的多執行緒其實是多程序,而現在意味著一個程序中有多個執行緒 使用多執行緒的原因(多執行緒的優點): 1.“節省”,啟動一個新的程序需要分配給它獨立的地
Linux多執行緒程式設計之員工資訊管理系統
員工資訊管理系統是基於Linux 多執行緒併發伺服器程式設計,由伺服器端和客戶端構成,客戶端可以執行在多個不同的主機上連線伺服器,伺服器對員工資訊的操作結果通過“員工資訊檔案”來儲存,即:“員工資訊”存放在後臺的檔案中,相當於資料庫。當用戶登入後,根據使用者名稱
linux多執行緒程式設計,你還在用sleep麼?用pthread_cond_timedwait吧
摘要:多執行緒程式設計中,執行緒A迴圈計算,然後sleep一會接著計算(目的是減少CPU利用率);存在的問題是,如果要關閉程式,通常選擇join執行緒A等待執行緒A退出,可是我們必須等到sleep函式返回,該執行緒A才能正常退出,這無疑減慢了程式退出的速度。當然,你可以terminate執行緒A,但這樣做
Linux多執行緒同步機制 .linux多執行緒程式設計機制
#include <stdio.h> #include <pthread.h> #include <unistd.h> pthread_mutex_t mutex; pthread_cond_t cond; void * child1(void *arg) {
(Linux多執行緒程式設計(不限Linux))多執行緒操作
——本文一個例子展開,介紹Linux下面執行緒的操作、多執行緒的同步和互斥。 前言 執行緒?為什麼有了程序還需要執行緒呢,他們有什麼區別?使用執行緒有什麼優勢呢?還有多執行緒程式設計的一些細節問題,如執行緒之間怎樣同步、互斥,這些東西將在本文中介紹。我在某QQ群裡見到這樣一道面試題: 是否熟悉POS
Linux多執行緒程式設計講解之系列六
執行緒安全介紹 在目前的電腦科學中,執行緒是作業系統排程的最小單元,程序是資源分配的最小單元。在大多數作業系統中,一個程序可以同時派生出多個執行緒。這些執行緒獨立執行,共享程序的資源。在單處理器系統中,多執行緒通過分時複用技術來技術,處理器在不同的執行緒間切換,從而更高效地利用系統 CPU資源。
Linux多執行緒程式設計講解之系列五
在Posix執行緒規範中還有幾個輔助函式難以歸類,暫且稱其為雜項函式,主要包括pthread_self()、pthread_equal()和pthread_once()三個,另外還有一個LinuxThreads非可移植性擴充套件函式pthread_kill_other_t
linux多執行緒程式設計(C):訊號量實現的執行緒安全佇列
用訊號量實現的執行緒安全佇列。 簡單有用的示例程式, 比起互斥量的實現在多執行緒時效率更好。 cir_queue.h /* * \File * cir_queue.h * \Brief * circular queue */#ifndef __CIR_QUEUE_H_
Linux多執行緒程式設計———重點區分
多執行緒 執行緒(thread)技術早在60年代就被提出,但真正應用多執行緒到作業系統中去,是在80年代中期,solaris是這方面的佼佼者.傳統的Unix也支援執行緒的概念,但是在一個程序(process)中只允許有一個執行緒,這樣多執行緒就意味著多程序.現
linux多執行緒程式設計——同步與互斥
我們在前面文章中已經分析了多執行緒VS多程序,也分析了執行緒的使用,現在我們來講解一下linux多執行緒程式設計之同步與互斥。 現在,我們不管究竟是多執行緒好還是多程序好,先講解一下,為什麼要使用多執行緒? 一、 為什麼要用多執行緒技術? 1、避免阻塞,大家知道,單個程序只
Linux多執行緒程式設計講解之系列一
Linux執行緒概述瞭解如何正確運用執行緒是每一個優秀程式設計師必備的素質。執行緒類似於程序。如同程序,執行緒由核心按時間分片進行管理。在單處理器系統中,核心使用時間分片來模擬執行緒的併發執行,這種方式和程序的相同。而在多處理器系統中,如同多個程序,執行緒實際上一樣可以併發
Linux多執行緒程式設計學習(1)--執行緒的概念和執行緒控制
Linux多執行緒程式設計學習總結 一.執行緒的概念 1.什麼是執行緒? 2.程序和執行緒的區別 3.程序和執行緒的關係 4.執行緒的優缺點 4.1 優點 4.2 缺點 5.執行
Linux多執行緒程式設計例項解析
Linux系統下的多執行緒遵循POSIX執行緒介面,稱為 pthread。編寫Linux下的多執行緒程式,需要使用標頭檔案pthread.h,連線時需要使用庫libpthread.a。順便說一下,Linux 下pthread的實現是通過系統呼叫clone()來實現的。clo
Linux多執行緒程式設計時如何檢視一個程序中的某個執行緒是否存活
pthread_kill: 別被名字嚇到,pthread_kill可不是kill,而是向執行緒傳送signal。還記得signal嗎,大部分signal的預設動作是終止程序的執行,所以,我們才要用signal()去抓訊號並加上處理函式。 int pthread_kil
linux多執行緒程式設計--對三層for迴圈的優化
目標:將下面3層for迴圈的程式碼進行優化: #include <iostream> #include <vector> #include <pthread.h> using namespace std; typedef vecto