qileilove

          blog已經(jīng)轉(zhuǎn)移至github,大家請(qǐng)?jiān)L問 http://qaseven.github.io/

          Linux多線程編程小結(jié)

          前一段時(shí)間由于開題的事情一直耽擱了我搞Linux的進(jìn)度,搞的我之前學(xué)的東西都遺忘了,非常煩躁的說,如今抽個(gè)時(shí)間把之前所學(xué)的做個(gè)小節(jié)。文章內(nèi)容主要總結(jié)于《Linux程序設(shè)計(jì)第3版》。
            1.Linux進(jìn)程與線程
            Linux進(jìn)程創(chuàng)建一個(gè)新線程時(shí),線程將擁有自己的棧(由于線程有自己的局部變量),但與它的創(chuàng)建者共享全局變量、文件描寫敘述符、信號(hào)句柄和當(dāng)前文件夾狀態(tài)。
            Linux通過fork創(chuàng)建子進(jìn)程與創(chuàng)建線程之間是有差別的:fork創(chuàng)建出該進(jìn)程的一份拷貝,這個(gè)新進(jìn)程擁有自己的變量和自己的PID,它的時(shí)間調(diào)度是獨(dú)立的,它的運(yùn)行差點(diǎn)兒全然獨(dú)立于父進(jìn)程。
            進(jìn)程能夠看成一個(gè)資源的基本單位,而線程是程序調(diào)度的基本單位,一個(gè)進(jìn)程內(nèi)部的線程之間共享進(jìn)程獲得的時(shí)間片。
            2._REENTRANT宏
            在一個(gè)多線程程序里,默認(rèn)情況下,僅僅有一個(gè)errno變量供全部的線程共享。在一個(gè)線程準(zhǔn)備獲取剛才的錯(cuò)誤代碼時(shí),該變量非常easy被還有一個(gè)線程中的函數(shù)調(diào)用所改變。相似的問題還存在于fputs之類的函數(shù)中,這些函數(shù)通經(jīng)常使用一個(gè)單獨(dú)的全局性區(qū)域來緩存輸出數(shù)據(jù)。
            為解決問題,須要使用可重入的例程。可重入代碼能夠被多次調(diào)用而仍然工作正常。編寫的多線程程序,通過定義宏_REENTRANT來告訴編譯器我們須要可重入功能,這個(gè)宏的定義必須出現(xiàn)于程序中的不論什么#include語句之前。
            _REENTRANT為我們做三件事情,而且做的很優(yōu)雅:
            (1)它會(huì)對(duì)部分函數(shù)又一次定義它們的可安全重入的版本號(hào),這些函數(shù)名字一般不會(huì)發(fā)生改變,僅僅是會(huì)在函數(shù)名后面加入_r字符串,如函數(shù)名gethostbyname變成gethostbyname_r。
            (2)stdio.h中原來以宏的形式實(shí)現(xiàn)的一些函數(shù)將變成可安全重入函數(shù)。
            (3)在error.h中定義的變量error如今將成為一個(gè)函數(shù)調(diào)用,它可以以一種安全的多線程方式來獲取真正的errno的值。
            3.線程的基本函數(shù)
            大多數(shù)pthread_XXX系列的函數(shù)在失敗時(shí),并未遵循UNIX函數(shù)的慣例返回-1,這樣的情況在UNIX函數(shù)中屬于一少部分。假設(shè)調(diào)用成功,則返回值是0,假設(shè)失敗則返回錯(cuò)誤代碼。
            1).線程創(chuàng)建:
            #include <pthread.h>
            int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
            參數(shù)說明:
            thread:指向pthread_create類型的指針,用于引用新創(chuàng)建的線程。
            attr:用于設(shè)置線程的屬性,一般不須要特殊的屬性,所以能夠簡(jiǎn)單地設(shè)置為NULL。
            *(*start_routine)(void *):傳遞新線程所要運(yùn)行的函數(shù)地址。
            arg:新線程所要運(yùn)行的函數(shù)的參數(shù)。
            調(diào)用假設(shè)成功,則返回值是0,假設(shè)失敗則返回錯(cuò)誤代碼。
            2).線程終止
            #include <pthread.h>
            void pthread_exit(void *retval);
            參數(shù)說明:
            retval:返回指針,指向線程向要返回的某個(gè)對(duì)象。
            線程通過調(diào)用pthread_exit函數(shù)終止運(yùn)行,并返回一個(gè)指向某對(duì)象的指針。注意:絕不能用它返回一個(gè)指向局部變量的指針,由于線程調(diào)用該函數(shù)后,這個(gè)局部變量就不存在了,這將引起嚴(yán)重的程序漏洞。
            3).線程同步
            #include <pthread.h>
            int pthread_join(pthread_t th, void **thread_return);
            參數(shù)說明:
            th:將要等待的張璐,線程通過pthread_create返回的標(biāo)識(shí)符來指定。
            thread_return:一個(gè)指針,指向還有一個(gè)指針,而后者指向線程的返回值。
            一個(gè)簡(jiǎn)單的多線程Demo(thread1.c):
            編譯這個(gè)程序時(shí),須要定義宏_REENTRANT:
            gcc -D_REENTRANT thread1.c -o thread1 –lpthread
            執(zhí)行這個(gè)程序:
            $ ./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!
            這個(gè)樣例值得我們?nèi)セ〞r(shí)間理解,由于它將作為幾個(gè)樣例的基礎(chǔ)。
            pthread_exit(void *retval)本身返回的就是指向某個(gè)對(duì)象的指針,因此,pthread_join(pthread_t th, void **thread_return);中的thread_return是二級(jí)指針,指向線程返回值的指針。
            能夠看到,我們創(chuàng)建的新線程改動(dòng)的數(shù)組message的值,而原先的線程也能夠訪問該數(shù)組。假設(shè)我們調(diào)用的是fork而不是pthread_create,就不會(huì)有這種效果了。原因是fork創(chuàng)建子進(jìn)程之后,子進(jìn)程會(huì)拷貝父進(jìn)程,兩者分離,相互不干擾,而線程之間則是共享進(jìn)程的相關(guān)資源。
           4.線程的同一時(shí)候運(yùn)行
            接下來,我們來編寫一個(gè)程序,以驗(yàn)證兩個(gè)線程的運(yùn)行是同一時(shí)候進(jìn)行的。當(dāng)然,假設(shè)是在一個(gè)單處理器系統(tǒng)上,線程的同一時(shí)候運(yùn)行就須要靠CPU在線程之間的高速切換來實(shí)現(xiàn)了。
            我們的程序須要利用一個(gè)原理:即除了局部變量外,全部其它的變量在一個(gè)進(jìn)程中的全部線程之間是共享的。
            在這個(gè)程序中,我們是在兩個(gè)線程之間使用輪詢技術(shù),這樣的方式稱為忙等待,所以它的效率會(huì)非常低。在本文的興許部分,我們將介紹一種更好的解決的方法。
            以下的代碼中,兩個(gè)線程會(huì)不斷的輪詢推斷flag的值是否滿足各自的要求。
            編譯這個(gè)程序:
            gcc -D_REENTRANT thread2.c -o thread2 –lpthread
            執(zhí)行這個(gè)程序:
            $ ./thread2
            121212121212121212
            Waiting for thread to finish...
            5.線程的同步
            在上述演示樣例中,我們採(cǎi)用輪詢的方式在兩個(gè)線程之間不停地切換是很笨拙且沒有效率的實(shí)現(xiàn)方式,幸運(yùn)的是,專門有一級(jí)設(shè)計(jì)好的函數(shù)為我們提供更好的控制線程運(yùn)行和訪問代碼臨界區(qū)的方法。
            本小節(jié)將介紹兩個(gè)線程同步的基本方法:信號(hào)量和相互排斥量。這兩種方法非常類似,其實(shí),它們能夠互相通過對(duì)方來實(shí)現(xiàn)。但在實(shí)際的應(yīng)用中,對(duì)于一些情況,可能使用信號(hào)量或相互排斥量中的一個(gè)更符合問題的語義,而且效果更好。
            5.1用信號(hào)量進(jìn)行同步
            1.信號(hào)量創(chuàng)建
            #include <semaphore.h>
            int sem_init(sem_t *sem, int pshared, unsigned int value);
            參數(shù)說明:
            sem:信號(hào)量對(duì)象。
            pshared:控制信號(hào)量的類型,0表示這個(gè)信號(hào)量是當(dāng)前進(jìn)程的局部信號(hào)量,否則,這個(gè)信號(hào)量就能夠在多個(gè)進(jìn)程之間共享。
            value:信號(hào)量的初始值。
            2.信號(hào)量控制
            #include <semaphore.h>
            int sem_wait(sem_t *sem);
            int sem_post(sem_t *sem);
            sem_post的作用是以原子操作的方式給信號(hào)量的值加1。
            sem_wait的作用是以原子操作的方式給信號(hào)量的值減1,但它會(huì)等到信號(hào)量非0時(shí)才會(huì)開始減法操作。假設(shè)對(duì)值為0的信號(hào)量調(diào)用sem_wait,這個(gè)函數(shù)就會(huì)等待,直到有線程添加了該信號(hào)量的值使其不再為0。
            3.信號(hào)量銷毀
            #include <semaphore.h>
            int sem_destory(sem_t *sem);
            這個(gè)函數(shù)的作用是,用完信號(hào)量后對(duì)它進(jìn)行清理,清理該信號(hào)量所擁有的資源。假設(shè)你試圖清理的信號(hào)量正被一些線程等待,就會(huì)收到一個(gè)錯(cuò)誤。
            與大多數(shù)Linux函數(shù)一樣,這些函數(shù)在成功時(shí)都返回0。
            以下編碼實(shí)現(xiàn)輸入字符串,統(tǒng)計(jì)每行的字符個(gè)數(shù),以“end”結(jié)束輸入:
            編譯這個(gè)程序:
            gcc -D_REENTRANT thread2.c -o thread2 –lpthread
            執(zhí)行這個(gè)程序:
          $ ./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
            通過使用信號(hào)量,我們堵塞了統(tǒng)計(jì)字符個(gè)數(shù)的線程,這個(gè)程序似乎對(duì)高速的文本輸入和悠閑的暫停都非常適用,比之前的輪詢解決方式效率上有了本質(zhì)的提高。 5.2用相互排斥量進(jìn)行線程同步
            還有一種用在多線程程序中同步訪問的方法是使用相互排斥量。它同意程序猿鎖住某個(gè)對(duì)象,使得每次僅僅能有一個(gè)線程訪問它。為了控制對(duì)關(guān)鍵代碼的訪問,必須在進(jìn)入這段代碼之前鎖住一個(gè)相互排斥量,然后在完畢操作之后解鎖它。
            用于相互排斥量的基本函數(shù)和用于信號(hào)量的函數(shù)很類似:
            #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);
            與其它函數(shù)一樣,成功時(shí)返回0,失敗時(shí)將返回錯(cuò)誤代碼,但這些函數(shù)并不設(shè)置errno,所以必須對(duì)函數(shù)的返回代碼進(jìn)行檢查。相互排斥量的屬性設(shè)置這里不討論,因此設(shè)置成NULL。
            我們用相互排斥量來重寫剛才的代碼例如以下:
            編譯這個(gè)程序:
            gcc -D_REENTRANT thread4.c -o thread4 –lpthread
            執(zhí)行這個(gè)程序:
          $ ./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.線程的屬性
            之前我們并未談及到線程的屬性,能夠控制的線程屬性是許多的,這里面僅僅列舉一些經(jīng)常使用的。
            如在前面的演示樣例中,我們都使用的pthread_join同步線程,但事實(shí)上有些情況下,我們并不須要。如:主線程為服務(wù)線程,而第二個(gè)線程為數(shù)據(jù)備份線程,備份工作完畢之后,第二個(gè)線程能夠直接終止了,它沒有必要再返回到主線程中。因此,我們能夠創(chuàng)建一個(gè)“脫離線程”。
            以下介紹幾個(gè)經(jīng)常使用的函數(shù):
            (1)int pthread_attr_init (pthread_attr_t* attr);
            功能:對(duì)線程屬性變量的初始化。
            attr:線程屬性。
            函數(shù)返回值:成功:0,失敗:-1
            (2) int pthread_attr_setscope (pthread_attr_t* attr, int scope);
            功能:設(shè)置線程綁定屬性。
            attr:線程屬性。
            scope:PTHREAD_SCOPE_SYSTEM(綁定);PTHREAD_SCOPE_PROCESS(非綁定)
            函數(shù)返回值:成功:0,失敗:-1
            (3) int pthread_attr_setdetachstate (pthread_attr_t* attr, int detachstate);
            功能:設(shè)置線程分離屬性。
            attr:線程屬性。
            detachstate:PTHREAD_CREATE_DETACHED(分離);PTHREAD_CREATE_JOINABLE(非分離)
            函數(shù)返回值:成功:0,失敗:-1
            (4) int pthread_attr_setschedpolicy(pthread_attr_t* attr, int policy);
            功能:設(shè)置創(chuàng)建線程的調(diào)度策略。
            attr:線程屬性;
            policy:線程調(diào)度策略:SCHED_FIFO、SCHED_RR和SCHED_OTHER。
            函數(shù)返回值:成功:0,失敗:-1
            (5) int pthread_attr_setschedparam (pthread_attr_t* attr, struct sched_param* param);
            功能:設(shè)置線程優(yōu)先級(jí)。
            attr:線程屬性。
            param:線程優(yōu)先級(jí)。
            函數(shù)返回值:成功:0,失敗:-1
            (6) int pthread_attr_destroy (pthread_attr_t* attr);
            功能:對(duì)線程屬性變量的銷毀。
            attr:線程屬性。
            函數(shù)返回值:成功:0,失敗:-1
            (7)其它
            int pthread_attr_setguardsize(pthread_attr_t* attr,size_t guardsize);//設(shè)置新創(chuàng)建線程棧的保護(hù)區(qū)大小。
            int pthread_attr_setinheritsched(pthread_attr_t* attr, int inheritsched);//決定如何設(shè)置新創(chuàng)建線程的調(diào)度屬性。
            int pthread_attr_setstack(pthread_attr_t* attr, void* stackader,size_t stacksize);//兩者共同決定了線程棧的基地址以及堆棧的最小尺寸(以字節(jié)為單位)。
            int pthread_attr_setstackaddr(pthread_attr_t* attr, void* stackader);//決定了新創(chuàng)建線程的棧的基地址。
            int pthread_attr_setstacksize(pthread_attr_t* attr, size_t stacksize);//決定了新創(chuàng)建線程的棧的最小尺寸(以字節(jié)為單位)。
            例:創(chuàng)建優(yōu)先級(jí)為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, &param);
          pthread_create(xxx, &attr, xxx, xxx);
          pthread_attr_destroy(&attr);
            以下實(shí)現(xiàn)一個(gè)脫離線程的程序,創(chuàng)建一個(gè)線程,其屬性設(shè)置為脫離狀態(tài)。子線程結(jié)束時(shí),要使用pthread_exit,原來的主線程不再等待與子線程又一次合并。代碼例如以下:
            編譯這個(gè)程序:
            gcc -D_REENTRANT thread5.c -o thread5 –lpthread
            執(zhí)行這個(gè)程序:
            $ ./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!
            通過設(shè)置線程的屬性,我們還能夠控制線程的調(diào)試,其方式與設(shè)置脫離狀態(tài)是一樣的。
            7.取消一個(gè)線程
            有時(shí),我們想讓一個(gè)線程能夠要求還有一個(gè)線程終止,線程有方法做到這一點(diǎn),與信號(hào)處理一樣,線程能夠在被要求終止時(shí)改變其行為。
            先來看用于請(qǐng)求一個(gè)線程終止的函數(shù):
            #include <pthread.h>
            int pthread_cancel(pthread_t thread);
            這個(gè)函數(shù)簡(jiǎn)單易懂,提供一個(gè)線程標(biāo)識(shí)符,我們就能夠發(fā)送請(qǐng)求來取消它。
            線程能夠用pthread_setcancelstate設(shè)置自己的取消狀態(tài)。
            #include <pthread.h>
            int pthread_setcancelstate(int state, int *oldstate);
            參數(shù)說明:
            state:能夠是PTHREAD_CANCEL_ENABLE同意線程接收取消請(qǐng)求,也能夠是PTHREAD_CANCEL_DISABLE忽略取消請(qǐng)求。
            oldstate:獲取先前的取消狀態(tài)。假設(shè)對(duì)它沒興趣,能夠簡(jiǎn)單地設(shè)置為NULL。假設(shè)取消請(qǐng)求被接受了,線程能夠進(jìn)入第二個(gè)控制層次,用pthread_setcanceltype設(shè)置取消類型。
            #include <pthread.h>
            int pthread_setcanceltype(int type, int *oldtype);
            參數(shù)說明:
            type:能夠取PTHREAD_CANCEL_ASYNCHRONOUS,它將使得在接收到取消請(qǐng)求后馬上採(cǎi)取行動(dòng);還有一個(gè)是PTHREAD_CANCEL_DEFERRED,它將使得在接收到取消請(qǐng)求后,一直等待直到線程運(yùn)行了下述函數(shù)之中的一個(gè)后才採(cǎi)取行動(dòng):pthread_join、pthread_cond_wait、pthread_cond_timedwait、pthread_testcancel、sem_wait或sigwait。
            oldtype:同意保存先前的狀態(tài),假設(shè)不想知道先前的狀態(tài),能夠傳遞NULL。
            默認(rèn)情況下,線程在啟動(dòng)時(shí)的取消狀態(tài)為PTHREAD_CANCEL_ENABLE,取消類型是PTHREAD_CANCEL_DEFERRED。
            以下編寫代碼thread6.c,主線程向它創(chuàng)建的線程發(fā)送一個(gè)取消請(qǐng)求。
            編譯這個(gè)程序:
            gcc -D_REENTRANT thread6.c -o thread6 –lpthread
            執(zhí)行這個(gè)程序:
            $ ./thread6
            thread_function is running...
            Thread is still running (0)...
            Thread is still running (1)...
            Thread is still running (2)...
            Thread is still running (3)...
            Canceling thread...
            Waiting for thread to finished...
            8.多線程
            之前,我們所編寫的代碼里面都不過創(chuàng)建了一個(gè)線程,如今我們來演示一下怎樣創(chuàng)建一個(gè)多線程的程序。
            編譯這個(gè)程序:
            gcc -D_REENTRANT thread7.c -o thread7 –lpthread
            執(zhí)行這個(gè)程序:
          $ ./thread7
          thread_function is running. Argument was 0
          thread_function is running. Argument was 1
          thread_function is running. Argument was 2
          thread_function is running. Argument was 3
          thread_function is running. Argument was 4
          Bye from 1
          thread_function is running. Argument was 5
          Waiting for threads to finished...
          Bye from 5
          Picked up a thread:6
          Bye from 0
          Bye from 2
          Bye from 3
          Bye from 4
          Picked up a thread:5
          Picked up a thread:4
          Picked up a thread:3
          Picked up a thread:2
          Picked up a thread:1
          All done
            9.小結(jié)
            本文主要介紹了Linux環(huán)境下的多線程編程,介紹了信號(hào)量和相互排斥量、線程屬性控制、線程同步、線程終止、取消線程及多線程并發(fā)。
            本文比較簡(jiǎn)單,僅僅作為初學(xué)Linux多線程編程入門之用。

          posted on 2014-11-06 10:42 順其自然EVO 閱讀(556) 評(píng)論(0)  編輯  收藏 所屬分類: linux

          <2014年11月>
          2627282930311
          2345678
          9101112131415
          16171819202122
          23242526272829
          30123456

          導(dǎo)航

          統(tǒng)計(jì)

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 潼关县| 麻江县| 秭归县| 当阳市| 女性| 琼海市| 图片| 徐水县| 东山县| 龙里县| 基隆市| 通辽市| 南平市| 塔河县| 盱眙县| 土默特左旗| 凤台县| 屯留县| 安达市| 五家渠市| 丰原市| 澜沧| 谢通门县| 边坝县| 杂多县| 高州市| 秭归县| 比如县| 东丰县| 札达县| 尤溪县| 合山市| 嫩江县| 从化市| 门头沟区| 樟树市| 文登市| 定日县| 六盘水市| 靖州| 清苑县|