Linux多線程編程快速入門
本文主要對Linux下的多線程進(jìn)行一個入門的介紹,雖然是入門,但是十分詳細(xì),希望大家通過本文所述,對Linux多線程編程的概念有一定的了解。具體如下。
1 線程基本知識
進(jìn)程是資源管理的基本單元,而線程是系統(tǒng)調(diào)度的基本單元,線程是操作系統(tǒng)能夠進(jìn)行調(diào)度運(yùn)算的最小單位,它被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位。一條線程指的是進(jìn)程中一個單一順序的控制流,一個進(jìn)程中可以并發(fā)多個線程,每條線程并行執(zhí)行不同的任務(wù)。
一個進(jìn)程在某一個時刻只能做一件事情,有了多個控制線程以后,在程序的設(shè)計成在某一個時刻能夠做不止一件事,每個線程處理獨(dú)自的任務(wù)。
需要注意的是:即使程序運(yùn)行在單核處理器上,也能夠得到多線程編程模型的好處。處理器的數(shù)量并不影響程序結(jié)構(gòu),所以不管處理器個數(shù)多少,程序都可以通過線程得以簡化。
linux操作系統(tǒng)使用符合POSIX線程作為系統(tǒng)標(biāo)準(zhǔn)線程,該P(yáng)OSIX線程標(biāo)準(zhǔn)定義了一整套操作線程的API。
2. 線程標(biāo)識
與進(jìn)程有一個ID一樣,每個線程有一個線程ID,所不同的是,進(jìn)程ID在整個系統(tǒng)中是唯一的,而線程是依附于進(jìn)程的,其線程ID只有在所屬的進(jìn)程中才有意義。線程ID用pthread_t表示。
//pthread_self直接返回調(diào)用線程的ID include <pthread.h> pthread_t pthread_self(void);
判斷兩個線程ID的大小是沒有任何意義的,但有時可能需要判斷兩個給定的線程ID是否相等,使用以下接口:
//pthread_equal如果t1和t2所指定的線程ID相同,返回0;否則返回非0值。 include <pthread.h> int pthread_equal(pthread_t t1, pthread_t t2);
3. 線程創(chuàng)建
一個線程的生命周期起始于它被創(chuàng)建的那一刻,創(chuàng)建線程的接口:
#include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
函數(shù)參數(shù):
thread(輸出參數(shù)),由pthread_create在線程創(chuàng)建成功后返回的線程句柄,該句柄在后續(xù)操作線程的API中用于標(biāo)志該新建的線程; start_routine(輸入?yún)?shù)),新建線程的入口函數(shù); arg(輸入?yún)?shù)),傳遞給新線程入口函數(shù)的參數(shù); attr(輸入?yún)?shù)),指定新建線程的屬性,如線程棧大小等;如果值為NULL,表示使用系統(tǒng)默認(rèn)屬性。
函數(shù)返回值:
成功,返回0; 失敗,返回相關(guān)錯誤碼。
需要注意:
1.主線程,這是一個進(jìn)程的初始線程,其入口函數(shù)為main函數(shù)。
2.新線程的運(yùn)行時機(jī),一個線程被創(chuàng)建之后有可能不會被馬上執(zhí)行,甚至,在創(chuàng)建它的線程結(jié)束后還沒被執(zhí)行;也有可能新線程在當(dāng)前線程從pthread_create前就已經(jīng)在運(yùn)行,甚至,在pthread_create前從當(dāng)前線程返回前新線程就已經(jīng)執(zhí)行完畢。
程序?qū)嵗?/p>
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> void printids(const char *s){ pid_t pid; pthread_t tid; pid = getpid(); tid = pthread_self(); printf("%s, pid %lu tid %lu (0x%lx)\n",s,(unsigned long)pid,(unsigned long)tid, (unsigned long)tid); } void *thread_func(void *arg){ printids("new thread: "); return ((void*)0); } int main() { int err; pthread_t tid; err = pthread_create(&tid,NULL,thread_func,NULL); if (err != 0) { fprintf(stderr,"create thread fail.\n"); exit(-1); } printids("main thread:"); sleep(1); return 0; }
注意上述的程序中,主線程休眠一秒,如果不休眠,則主線程不休眠,則其可能會退出,這樣新線程可能不會被運(yùn)行,我自己注釋掉sleep函數(shù),發(fā)現(xiàn)好多次才能讓新線程輸出。
編譯命令:
gcc -o thread thread.c -lpthread
運(yùn)行結(jié)果如下:
main thread:, pid 889 tid 139846854309696 (0x7f30a212f740) new thread: , pid 889 tid 139846845961984 (0x7f30a1939700)
可以看到兩個線程的進(jìn)程ID是相同的。其共享進(jìn)程中的資源。
4. 線程終止
線程的終止分兩種形式:被動終止和主動終止
被動終止有兩種方式:
1.線程所在進(jìn)程終止,任意線程執(zhí)行exit、_Exit或者_(dá)exit函數(shù),都會導(dǎo)致進(jìn)程終止,從而導(dǎo)致依附于該進(jìn)程的所有線程終止。
2.其他線程調(diào)用pthread_cancel請求取消該線程。
主動終止也有兩種方式:
1.在線程的入口函數(shù)中執(zhí)行return語句,main函數(shù)(主線程入口函數(shù))執(zhí)行return語句會導(dǎo)致進(jìn)程終止,從而導(dǎo)致依附于該進(jìn)程的所有線程終止。
2.線程調(diào)用pthread_exit函數(shù),main函數(shù)(主線程入口函數(shù))調(diào)用pthread_exit函數(shù), 主線程終止,但如果該進(jìn)程內(nèi)還有其他線程存在,進(jìn)程會繼續(xù)存在,進(jìn)程內(nèi)其他線程繼續(xù)運(yùn)行。
線程終止函數(shù):
include <pthread.h> void pthread_exit(void *retval);
線程調(diào)用pthread_exit函數(shù)會導(dǎo)致該調(diào)用線程終止,并且返回由retval指定的內(nèi)容。
注意:retval不能指向該線程的??臻g,否則可能成為野指針!
5. 管理線程的終止
5.1 線程的連接
一個線程的終止對于另外一個線程而言是一種異步的事件,有時我們想等待某個ID的線程終止了再去執(zhí)行某些操作,pthread_join函數(shù)為我們提供了這種功能,該功能稱為線程的連接:
include <pthread.h> int pthread_join(pthread_t thread, void **retval);
參數(shù)說明:
thread(輸入?yún)?shù)),指定我們希望等待的線程 retval(輸出參數(shù)),我們等待的線程終止時的返回值,就是在線程入口函數(shù)中return的值或者調(diào)用pthread_exit函數(shù)的參數(shù)
返回值:
成功時,返回0 錯誤時,返回正數(shù)錯誤碼
當(dāng)線程X連接線程Y時,如果線程Y仍在運(yùn)行,則線程X會阻塞直到線程Y終止;如果線程Y在被連接之前已經(jīng)終止了,那么線程X的連接調(diào)用會立即返回。
連接線程其實(shí)還有另外一層意義,一個線程終止后,如果沒有人對它進(jìn)行連接,那么該終止線程占用的資源,系統(tǒng)將無法回收,而該終止線程也會成為僵尸線程。因此,當(dāng)我們?nèi)ミB接某個線程時,其實(shí)也是在告訴系統(tǒng)該終止線程的資源可以回收了。
注意:對于一個已經(jīng)被連接過的線程再次執(zhí)行連接操作, 將會導(dǎo)致無法預(yù)知的行為!
5.2 線程的分離
有時我們并不在乎某個線程是不是已經(jīng)終止了,我們只是希望如果某個線程終止了,系統(tǒng)能自動回收掉該終止線程所占用的資源。pthread_detach函數(shù)為我們提供了這個功能,該功能稱為線程的分離:
#include <pthread.h> int pthread_detach(pthread_t thread);
默認(rèn)情況下,一個線程終止了,是需要在被連接后系統(tǒng)才能回收其占有的資源的。如果我們調(diào)用pthread_detach函數(shù)去分離某個線程,那么該線程終止后系統(tǒng)將自動回收其資源。
/* * 文件名: thread_sample1.c * 描述:演示線程基本操作 */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> /*子線程1入口函數(shù)*/ void *thread_routine1(void *arg) { fprintf(stdout, "thread1: hello world!\n"); sleep(1); /*子線程1在此退出*/ return NULL; } /*子線程2入口函數(shù)*/ void *thread_routine2(void *arg) { fprintf(stdout, "thread2: I'm running...\n"); pthread_t main_thread = (pthread_t)arg; /*分離自我,不能再被連接*/ pthread_detach(pthread_self()); /*判斷主線程ID與子線程2ID是否相等*/ if (!pthread_equal(main_thread, pthread_self())) { fprintf(stdout, "thread2: main thread id is not equal thread2\n"); } /*等待主線程終止*/ pthread_join(main_thread, NULL); fprintf(stdout, "thread2: main thread exit!\n"); fprintf(stdout, "thread2: exit!\n"); fprintf(stdout, "thread2: process exit!\n"); /*子線程2在此終止,進(jìn)程退出*/ pthread_exit(NULL); } int main(int argc, char *argv[]) { /*創(chuàng)建子線程1*/ pthread_t t1; if (pthread_create(&t1, NULL, thread_routine1, NULL)!=0) { fprintf(stderr, "create thread fail.\n"); exit(-1); } /*等待子線程1終止*/ pthread_join(t1, NULL); fprintf(stdout, "main thread: thread1 terminated!\n\n"); /*創(chuàng)建子線程2,并將主線程ID傳遞給子線程2*/ pthread_t t2; if (pthread_create(&t2, NULL, thread_routine2, (void *)pthread_self())!=0) { fprintf(stderr, "create thread fail.\n"); exit(-1); } fprintf(stdout, "main thread: sleeping...\n"); sleep(3); /*主線程使用pthread_exit函數(shù)終止,進(jìn)程繼續(xù)存在*/ fprintf(stdout, "main thread: exit!\n"); pthread_exit(NULL); fprintf(stdout, "main thread: never reach here!\n"); return 0; }
最終的執(zhí)行結(jié)果如下:
thread1: hello world! main thread: thread1 terminated! main thread: sleeping... thread2: I'm running... thread2: main thread id is not equal thread2 main thread: exit! thread2: main thread exit! thread2: exit! thread2: process exit!
總結(jié)
以上就是本文關(guān)于Linux多線程編程快速入門的全部內(nèi)容,希望對大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站其他相關(guān)專題,如有不足之處,歡迎留言指出。感謝朋友們對本站的支持!
相關(guān)文章
Linux使用MySQL忘記root密碼及修改MySQL默認(rèn)編碼
本篇文章主要介紹了Linux使用MySQL忘記root密碼及修改MySQL默認(rèn)編碼,碰到這個問題的朋友可以參考下。2016-10-10linux 程序、動態(tài)庫、靜態(tài)庫內(nèi)部添加版本號和編譯時間詳解
下面小編就為大家?guī)硪黄猯inux 程序、動態(tài)庫、靜態(tài)庫內(nèi)部添加版本號和編譯時間詳解。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-12-12IO多路復(fù)用之select全面總結(jié)(必看篇)
下面小編就為大家?guī)硪黄狪O多路復(fù)用之select全面總結(jié)(必看篇)。小編覺得挺不錯的?,F(xiàn)在就分享給大家。也給大家做個參考。一起跟隨小編過來看看吧2016-12-12探討如何減少Linux服務(wù)器TIME_WAIT過多的問題
本篇文章是對如何減少Linux服務(wù)器TIME_WAIT過多的問題進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06linux操作系統(tǒng)下配置ssh/sftp和權(quán)限設(shè)置方法
這篇文章主要介紹了linux操作系統(tǒng)下配置ssh/sftp和權(quán)限設(shè)置方法 ,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2018-11-11查看linux ssh服務(wù)信息及運(yùn)行狀態(tài)方法
今天小編就為大家分享一篇查看linux ssh服務(wù)信息及運(yùn)行狀態(tài)方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-07-07