線程控制原語之pthread_self和pthread_create函數

注意:使用線程庫函數用gcc編譯時,要加參數:-lpthreadlibpthread.so,因為線程庫函數屬于第三方c庫函數,不是標準庫函數(/lib、/usr/lib或者/usr/local/lib)。

1pthread_self函數

獲取線程ID。其作用對應進程中 getpid() 函數。

pthread_t pthread_self(void);?? 返回值:成功:調用該函數的線程ID;失敗:永遠不會失敗。

pthread_t:typedef? unsigned long int? pthread_t;? 輸出格式格式為:%lu

線程ID是進程內部,識別標志。(兩個進程間,線程ID允許相同)

注意:不應使用全局變量 pthread_t tid(因為全局變量被一個進程中的多個線程共享),在子線程中通過pthread_create傳出參數來獲取線程ID或者使用pthread_self。

2pthread_create函數

創建一個新線程。其作用對應進程中fork() 函數。

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

返回值:成功:0;失敗:錯誤編號。Linux環境下,所有線程特點,失敗均直接返回錯誤編號,即直接返回errno number。

參數1:傳出參數,新建線程的線程ID

參數2:傳入參數,通常傳NULL,表示使用線程默認屬性。若想使用具體屬性也可以修改該參數。

參數3:函數指針,指向線程主控函數(線程體),該函數運行結束,則線程結束。

參數4:線程主函數執行期間所使用的參數,若不同參數傳NULL。

在一個線程中調用pthread_create()創建新的線程后,當前線程從pthread_create()返回繼續往下執行,而新的線程所執行的代碼由我們傳給pthread_create的函數指針start_routine決定(線程創建成功后就立即去執行start_routine函數)。start_routine函數接收一個參數,是通過pthread_create的arg參數傳遞給它的,該參數的類型為void *,這個指針按什么類型解釋由調用者自己定義。start_routine的返回值類型也是void *,這個指針的含義同樣由調用者自己定義。start_routine返回時,這個線程就退出了,其它線程可以調用pthread_join得到start_routine的返回值,類似于父進程調用wait( )得到子進程的退出狀態,稍后詳細介紹pthread_join。

pthread_create成功返回后,新創建的線程的id被填寫到thread參數所指向的內存單元。我們知道進程id的類型是pid_t,每個進程的id在整個系統中是唯一的,調用getpid( )可以獲得當前進程的id,是一個正整數值。線程id的類型是pthread_t,它只在當前進程中保證是唯一的,在不同的系統中pthread_t這個類型有不同的實現,它可能是一個整數值,也可能是一個結構體,也可能是一個地址,所以不能簡單地當成整數(%lu)用printf打印,調用pthread_self( )可以獲得當前線程的id

attr參數表示線程屬性,本節不深入討論線程屬性,所有代碼例子都傳NULL給attr參數,表示線程屬性取缺省值,感興趣的讀者可以參考APUE。

//pthread_selfpthread_create函數示例

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>void *tfn(void *arg)
{printf("In thread, the process id is %d and thread id is %lu.\n",getpid( ),pthread_self( ));return NULL;
}int main(void)
{pthread_t tid;int ret;printf("In main1, the process id is %d and thread id is %lu.\n",getpid( ),pthread_self( ));ret = pthread_create(&tid, NULL, tfn, NULL);if( ret != 0 )   //出錯判斷{fprintf(stderr,"pthread_create error: %s\n",strerror(ret));exit(1);}printf("the created thread's id is %lu\n",tid);sleep(1);  //注意一定要睡1sprintf("In main2, the process id is %u and thread id is %lu.\n",getpid( ),pthread_self( ));return 0;
}

[root@localhost 01_pthread_test]# ./pthrd_crt

In main1, the process id is 9814 and thread id is 4151932608.

the created thread's id is 4149869376

In thread, the process id is 9814 and thread id is 4149869376.

In main2, the process id is 9814 and thread id is 4151932608.

分析:

  1. main函數中調用sleep函數的原因。在Linux環境中,進程的本質就是含有一個線程的進程,其在創建線程之前就包含一個線程,因此此時有一個進程ID和一個線程ID(當然也有一個線程號),兩個ID不想等,將此時的線程稱為主控線程,后面創建的線程稱為子線程。主線程創建完子線程后,如果主線程提前就結束了(執行了return或者exit等語句),則此時進程的地址空間會被回收,因此子線程也就被終止了(因為線程共享地址空間)。因此,主線程(main函數)必須要睡1s等待子線程完成工作后再結束。
  2. 注意線程庫函數調用失敗返回值的判斷。出錯不會再置ennro的值為相應的值,而是直接輸出錯誤編號,因此不能再用perror函數來輸出詳細錯誤信息(該函數是根據errno的值輸出相應的錯誤信息)。可以采用strerror函數,其作用:返回一個字符串,這個字符串描述了錯誤編號所對應的錯誤信息。char * strerror(int errnum);???? 如上所示:fprintf(stderr,"pthread_create error: %s\n",strerror(ret)); 采用fprintf函數將錯誤信息輸出。相對于printf,fprintf函數可以將內容輸出到指定文件中,第一個參數為文件的結構體指針。printf與fprintf都是標準輸出,stdin、stdout和stderr為標準輸入、標準輸出和標準錯誤輸出對應的文件結構體。
  3. 輸出結果可以看出:在創建子線程前,進程本身包含一個線程;同一個進程中的所有線程的進程ID都一樣;線程ID明顯比線程號和進程ID大得多。

總結:由于pthread_create的錯誤碼不保存在errno中,因此不能直接用perror( )打印錯誤信息,可以先用strerror( )把錯誤碼轉換成錯誤信息再打印。為了防止新創建的線程還沒有得到執行就終止,我們在main函數return之前延時1秒,這只是一種權宜之計,即使主線程等待1秒,內核也不一定會調度新創建的線程執行,后面會有更好的辦法。

//循環創建n個子線程的架構

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>void *tfn(void *arg)
{int s = (int)arg;sleep(s);printf("The %dth thread: the process id is %d and thread id is %lu.\n",s+1,getpid( ),pthread_self( ));return NULL;
}int main(void)
{pthread_t tid;int ret, i;for( i=0;i<5;i++ ){ret = pthread_create(&tid, NULL, tfn,(void *)i);if( ret != 0 ){fprintf(stderr,"pthread_create error: %s\n",strerror(ret));exit(1);}}sleep(i);printf("In main: the process id is %u and thread id is %lu.\n",getpid( ),pthread_self( ));return 0;
}

[root@localhost 01_pthread_test]# ./pthrd_crt

The 1th thread: the process id is 4026 and thread id is 4149246784.

The 2th thread: the process id is 4026 and thread id is 4140854080.

The 3th thread: the process id is 4026 and thread id is 4132461376.

The 4th thread: the process id is 4026 and thread id is 4124068672.

The 5th thread: the process id is 4026 and thread id is 4115675968.

In main: the process id is 4026 and thread id is 4151466240.

分析:

  1. 如上循環創建5個線程,該進程中有6個線程,進程ID均相同,線程ID不一樣;
  2. 每一個進程中線程的數目是有限的,不能超過這個值;
  3. 特別強調:主控線程調用的pthread_create函數中的第4個參數(void *)i不能改為:(void *)&i,相應在子線程執行的函數tfn中為s = *( (int *)arg ); 詳細分析如下:首先,main函數是主控線程執行的函數,因此位于主控線程的用戶棧幀空間中,該空間保存了main函數中的局部變量和形參值:tid、red、i,當main函數又去調用pthread_create函數時,main函數的棧幀頭尾作為臨時值保存在棧幀空間中。pthread_create函數的形參值保存在它的棧幀空間中。對于tfn函數(子線程執行的函數),它的局部變量和形參值(s和arg)又保存在各個線程的用戶棧空間中,相互獨立。外界參數的值傳遞給函數的形參時,都是按值傳遞(指針則傳地址值,非指針變量則傳變量值),如果傳參數(void *)i,則將數值i轉變為指針值 (指針值與數值i在大小上是相等的,只是在32位系統中指針值和整型i都占據4個字節;在64位系統中,指針值占據8個字節,而整型i依然占據4個字節,此時高位補0即可。然后將該指針值賦值給指針變量arg,即arg的值大小上是等于i的,但arg位于子線程的用戶棧空間中,然后:(int)arg;即把指針值轉變為整型s,s當然等于i,因此該方式是正確的。在64位系統中,指針值轉變為整型i,8字節變為4字節,截取高位,而高位都是0,因此無影響。但如果是:(void *)&i ?*( (int *)arg),這種情況下傳遞的是i的地址給arg,因此在子線程中是通過i的地址來訪問數值i的,而數值i在主控線程中是會隨時發生變化的(for循環中的i++導致),因此該方式不成立。
  4. pthread_create函數是一個回調函數,若創建子線程成功,則會去調用相應的函數;
  5. 子線程執行完自己的函數后返回的值通過pthread_join函數回收;
  6. return的作用是返回到函數的調用點,如果是main函數中的return,則代表該進程結束,并釋放進程地址空間,所有線程都終止。對于其它函數的return,則直接返回到函數的調用點。exit和_exit函數會直接終止整個進程,導致所有線程結束。pthread_exit函數則會導致調用該函數的線程結束。所以,多線程環境中,應盡量少用,或者不使用exit函數,取而代之使用pthread_exit函數,將單個線程退出。任何線程里exit導致進程退出,其他線程也結束,主控線程退出時不能return或exit。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/385302.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/385302.shtml
英文地址,請注明出處:http://en.pswp.cn/news/385302.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

1005. 繼續(3n+1)猜想 (25)

卡拉茲(Callatz)猜想已經在1001中給出了描述。在這個題目里&#xff0c;情況稍微有些復雜。 當我們驗證卡拉茲猜想的時候&#xff0c;為了避免重復計算&#xff0c;可以記錄下遞推過程中遇到的每一個數。例如對n3進行驗證的時候&#xff0c;我們需要計算3、5、8、4、2、1&#…

C指針深度解析

&#xff08;1&#xff09;指針的概念 指針是一種數據類型&#xff0c;而內存地址是這種數據類型具體的值&#xff08;注意區分兩者的概念&#xff09;。先說一下什么是內存地址&#xff1a;假設CPU的尋址方式是以字節尋址的&#xff0c;即每一個字節對應一個地址編號&#xf…

1007. 素數對猜想

讓我們定義 dn 為&#xff1a;dn pn1 - pn&#xff0c;其中 pi 是第i個素數。顯然有 d11 且對于n>1有 dn 是偶數。“素數對猜想”認為“存在無窮多對相鄰且差為2的素數”。 現給定任意正整數N (< 105)&#xff0c;請計算不超過N的滿足猜想的素數對的個數。 輸入格式&…

線程共享全局變量(.data和.bbs)

線程默認共享數據段、代碼段等地址空間&#xff0c;常用的是全局變量。而進程不共享全局變量&#xff0c;只能借助mmap。 //代碼示例 #include <string.h> #include <stdio.h> #include <pthread.h> #include <stdlib.h> #include <string.h> …

1008 數組元素循環右移問題 (20)

一個數組A中存有N&#xff08;N&gt0&#xff09;個整數&#xff0c;在不允許使用另外數組的前提下&#xff0c;將每個整數循環向右移M&#xff08;M>0&#xff09;個位置&#xff0c;即將A中的數據由&#xff08;A~0~ A~1~……A~N-1~&#xff09;變換為&#xff08;A~N-…

C++設計模式之策略模式(Strategy)

Strategy策略模式作用&#xff1a;定義了算法家族&#xff0c;分別封裝起來&#xff0c;讓他們之間可以互相替換&#xff0c;此模式讓算法的變化&#xff0c;不會影響到使用算法的客戶。 UML圖&#xff1a; 代碼實現 #include <iostream> using namespace std;class St…

pthread_exit函數

void pthread_exit(void *retval); 參數&#xff1a;retval表示線程退出狀態&#xff0c;通常傳NULL。 作用&#xff1a;將單個線程退出。 注意幾點&#xff1a; return的作用是返回到函數的調用點&#xff0c;如果是main函數中的return&#xff0c;則代表該進程結束&#x…

C++面試常見問題

背景色yellow 1 1. c如何防止一個類被其他類繼承 >- 如果是僅僅為了達到這個目的可以直接把這個類的構造函數設置成私有的&#xff0c;這樣就杜絕了其他類的繼承。也相當于毀掉了這個類&#xff08;無法再創造出自己的對象&#xff09;。 那么怎么樣既要保證這個類的完整性…

pthread_join函數

int pthread_join(pthread_t thread, void **retval); 作用&#xff1a;阻塞等待線程退出&#xff0c;獲取線程退出狀態。其作用對應進程中 waitpid() 函數。 成功&#xff1a;0&#xff1b;失敗&#xff1a;錯誤號 strerror函數 參數&#xff1a;thread&#xff1a;線程I…

【C++ Priemr | 15】派生類向基類轉換的可訪問性

1. 只有當D公有繼承B時&#xff0c;用戶代碼才能使用派生類向基類的轉換&#xff1b;如果D私有繼承B的方式是受保護的或者私有的&#xff0c;則用戶代碼不能使用該轉換。 class A {}&#xff1b; class B : public A {}void function(const A&) {}int main() {B b;functio…

pthread_detach函數

int pthread_detach(pthread_t thread); 成功&#xff1a;0&#xff1b;失敗&#xff1a;錯誤號 作用&#xff1a;從狀態上實現線程分離&#xff0c;注意不是指該線程獨自占用地址空間。 線程分離狀態&#xff1a;指定該狀態&#xff0c;線程主動與主控線程斷開關系。線程…

【C++ Primer | 15】面試問題

在成員函數中調用虛函數 #include <iostream> using namespace std; class CBase { public:void func1(){func2();}virtual void func2() {cout << "CBase::func2()" << endl;} }; class CDerived:public CBase { public:virtual void func2() {…

pthread_cancel、pthread_equal函數

&#xff08;1&#xff09;pthread_cancel函數 int pthread_cancel(pthread_t thread); 成功&#xff1a;0&#xff1b;失敗&#xff1a;錯誤號 作用&#xff1a;殺死(取消)線程&#xff0c;其作用對應進程中 kill() 函數。 注意&#xff1a;線程的取消并不是實時的&…

STL源碼剖析面試問題

當vector的內存用完了&#xff0c;它是如何動態擴展內存的&#xff1f;它是怎么釋放內存的&#xff1f;用clear可以釋放掉內存嗎&#xff1f;是不是線程安全的&#xff1f; vector內存用完了&#xff0c;會以當前size大小重新申請2* size的內存&#xff0c;然后把原來的元素復制…

線程與進程的控制原語對比

線程與進程的控制原語對比 fork pthead_create exit( int ) pthead_exit(void *); wait(int *) pthread_join&#xff08; ,void **&#xff09; 阻塞 ;分離 22 &#xff1b;cancel -1 kill() pthread_cancel(); 取消點(檢查點)&#xff1a;系統調用 getpid() pthrea…

ptmalloc堆內存管理機制(主要討論Linux x86下32位系統)

bin&#xff08;chunk容器&#xff09; ptmalloc將相似大小的 chunk 用雙向鏈表鏈接起來&#xff0c;這樣的一個鏈表被稱為一個 bin。 Ptmalloc 一共維護了 128 個 bin&#xff0c;并使用一個數組來存儲這些 bin&#xff0c;這個數組被成為bin數組。 bin數組結構如下&#xf…

線程屬性的修改

&#xff08;1&#xff09;線程屬性 Linux下線程的屬性是可以根據實際項目需要&#xff0c;進行設置&#xff0c;之前我們討論的線程都是采用線程的默認屬性&#xff0c;默認屬性已經可以解決絕大多數開發時遇到的問題。如我們對程序的性能提出更高的要求那么需要設置線程屬性…

NPTL(Native POSIX Thread Library)

1.NPTL&#xff08;Native POSIX Thread Library&#xff09;為POSIX標準線程庫&#xff0c;查看當前Linux系統的pthread庫&#xff08;線程庫&#xff09;版本的命令為&#xff1a;getconf GNU_LIBPTHREAD_VERSION。 [rootlocalhost 01_pthread_test]# getconf GNU_LIBPTHREA…

線程使用注意事項

1.主線程退出其他線程不退出&#xff0c;主線程應調用pthread_exit&#xff1b; 2.避免僵尸線程&#xff1a;pthread_join、pthread_detach、pthread_create指定分離屬性。被join線程可能在join函數返回前就釋放完自己的所有內存資源&#xff0c;所以不應當返回被回收線程棧中…

線程同步的概念

所謂同步&#xff0c;即同時起步&#xff0c;協調一致。不同的對象&#xff0c;對“同步”的理解方式略有不同。如&#xff0c;設備同步&#xff0c;是指在兩個設備之間規定一個共同的時間參考&#xff1b;數據庫同步&#xff0c;是指讓兩個或多個數據庫內容保持一致&#xff0…