日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

Linux 高性能服务器编程——多线程编程

發布時間:2025/3/8 54 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux 高性能服务器编程——多线程编程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
問題聚焦:? ? 在簡單地介紹線程的基本知識之后,主要討論三個方面的內容:
? ? 1 創建線程和結束線程;
? ? 2 讀取和設置線程屬性;
? ? 3 線程同步方式:POSIX信號量,互斥鎖和條件變量。



Linux線程概述
線程模型程序中完成一個獨立任務的完整執行序列,即一個可調度的實體。分為內核線程和用戶線程當進程的一個內核線程獲得CPU的使用權時,它就加載并運行一個用戶線程,可見,內核線程相當于用戶線程運行的“容器”。一個進程可以擁有M個內核線程和N個用戶線程, M<=N。
線程實現完全在用戶空間實現線程的特點:
  • 創建和調度線程都無須內核的干預,因此速度相當快。
  • 不占用額外的內核資源,很多線程不會對系統性能造成明顯影響。
  • (缺點)一個進程的多個線程無法運行在不同的CPU上

完全在內核空間實現線程的優缺點則和上面的實現相反,優缺點也互換。雙層調度模式是前兩種實現模式的混合體
  • 內核調度M個內核線程,線程庫調度N個用戶線程
  • 不會過度消耗內核資源,又可以充分利用多處理器的優勢



創建線程和結束線程
基礎API
創建pthread_create定義:#include <pthread.h> int pthread_create ( pthread_t* thread, const pthread_attr_t* attr,void * (*start_routine)(void*) , void* arg);參數說明:thread:新線程的標識符, 實際是一個整型,并且,Linux上幾乎所有的資源標識符都是一個整型數,比如socket。attr:用于設置新線程的屬性,傳遞NULL表示使用默認線程屬性start_routing和arg:分別指定新線程將運行的函數及其參數。返回:成功時返回0,失敗時返回錯誤碼。一個用戶可以打開的線程數量不能超過RLIMIT_NPROC軟資源限制。此外,系統上所有能創建的線程總數也不能超過/proc/sys/kernel/threads-max 內核參數所定義的值。
退出線程pthread_exit定義:#include <pthread.h> void pthread_exit ( void* retval );pthread_exit函數通過retval參數向線程的回收者傳遞其退出信息。它執行完之后不會返回到調用者,而且永遠不會失敗。
回收其他線程pthread_join定義:#include <pthread.h> int pthread_join( pthread_t thread, void** retval );? ? ? ?一個進程中的所有線程都可以調用pthread_join函數來回收其他線程,即等待其他線程結束,這類似于回收進程的wait和waitpid系統調用。
參數說明:thread:目標線程的標識符retval:目標線程返回的退出信息效果:該函數會一直阻塞,直到被回收的線程結束為止返回:成功時返回0,失敗時返回錯誤碼。

取消線程pthread_cancel功能:
? ? ? ?有時候我們希望異常終止一個線程,即取消線程。定義:#include <pthread.h> int pthread_cancel ( pthread_t thread );? ? ? ?接收到取消請求的目標線程可以決定是否允許被取消以及如何取消。這分別由如下兩個函數完成:
#include <pthread.h> int pthread_setcancelstate( int state, int *oldstate ); int pthread_setcanceltype ( int type, int *oldtype );這兩個函數的第一個參數分別用于設置線程的取消狀態(是否允許取消)和取消類型(如何取消)。第二個參數分別記錄線程原來的取消狀態和取消類型。
state參數有兩個可選值:
  • PTHREAD_CANCEL_ENABLE:允許線程被取消。它是線程被創建時默認取消狀態。
  • PTHREAD_CANCEL_DISABLE:禁止線程被取消。這種情況下,如果一個線程收到取消請求,則它會將請求掛起,直到該線程允許被取消。
type參數也有兩個可選值:
  • PTHREAD_CANCEL_ASYNCHRONOUS:線程隨時都可以被取消。它將使得接收到取消請求的目標線程立即采取行動。
  • PTHREAD_CANCEL_DEFERRED:允許目標線程推遲行動,直到它調用了下面幾個所謂的取消點函數中的一個:pthread_join、pthread_testcancel、pthread_cond_wait、pthread_cond_timewait、sem_wait和sigwait。
pthread_setcancelstate和pthread_setcanceltype 成功時返回0,失敗則返回錯誤碼。



線程屬性
pthread_attr_t結構體,完整的線程屬性。定義:#include <bits/pthreadtypes.h> #define __SIZEOF_PTHREAD_ATTR_T 36 typedef union {char __size[__SIZEOF_PTHREAD_ATTR_T];long int __align; } pthread_attr_t;各種線程屬性全部包含在一個字符數組中,并且線程庫定義了一系列函數操作pthread_attr_t類型的變量,以方便我們獲取和設置線程屬性。這些函數包括:#include <pthread.h> /*初始化線程屬性對象*/ int pthread_attr_init( pthread_attr_t* attr ); /*銷毀線程屬性對象,被銷毀的線程屬性對象只有再次初始化之后才能繼續使用*/ int pthread_attr_destroy( pthread_attr_t* attr); /*下面這些函數用于獲取和設置線程屬性對象的某個屬性*/ int pthread_attr_getdetachstate( const pthread_attr_t* attr ,int* detachstate ); int pthread_attr_setdetachstate( pthread_attr_t* attr,int detachstate ); int pthread_attr_getstackaddr( const pthread_attr_t* attr,void **stackaddr ); int pthread_attr_setstackaddr( pthread_attr_t* attr,void* stackaddr ); int pthread_attr_getstacksize( const pthread_attr_t* attr,size_t* stacksize ); int pthread_attr_setstacksize( pthread_attr_t* attr,size_t stackszie ); int pthread_attr_getstack( const pthread_attr_t* attr,void** stackaddr,size_t* stacksize ); int pthread_attr_setstack( pthread_attr_t* attr,void* stackaddr,size_t stacksize ); int pthread_attr_getguardsize( const pthread_attr_t* attr,size_t* guarsize ); int pthread_attr_setguardsize( pthread_attr_t* attr,size_t guarsize ); int pthread_attr_getschedparam( const pthread_attr_t* attr,struct sched_param* param ); int pthread_attr_setschedparam( pthread_attr_t* attr,const struct sched_param* param ); int pthread_attr_getschedpolicy( const pthread_attr_t* attr,int* policy ); int pthread_attr_setschedpolicy( pthread_attr_t* attr,int policy ); int pthread_attr_getinheritsched( const pthread_attr_t* attr,int* inherit ); int pthread_attr_setinheritsched( pthread_attr_t* attr,int inherit ); int pthread_attr_getscope( const pthread_attr_t* attr,int* scope ); int pthread_attr_setscope( pthread_attr_t* attr,int scope );
下面我們詳細討論每個線程屬性的含義:
  • detachstate:線程的脫離狀態。它有PTHREAD_CREATE_JOINABLE和PTHREAD_CREATE_DETACH兩個可選值。前者指定線程是可以被回收的,后者使調用線程脫離與進程中其他線程的同步。脫離了與其他線程同步的線程稱為“脫離線程”。脫離線程在退出時將自行釋放其占用的系統資源。線程創建時該屬性的默認值是PTHREAD_CREATE_JOINABLE。
  • stackaddr和stacksize:線程堆棧的起始地址和大小。一般來說,我們不需要自己來管理線程堆棧,因為Linux默認為每個線程分配了足夠的堆棧空間(一般是8 MB)。我們可以使用ulimt -s 命令查看或修改這個默認值。
  • guardsize:保護區域大小。如果guardsize大于0,則系統創建線程的時候會在其堆棧的尾部額外分配guardsize字節的空間,作為保護堆棧不被錯誤的覆蓋的區域。如果guardsize等于0,則系統不為新創建的線程設置堆棧保護區。如果使用者通過pthread_attr_setstackaddr或pthread_attr_setstack函數手動設置線程的堆棧,則guardsize屬性將被忽略。
  • schedparam:線程調度參數。其類型時sched_param結構體。該結構體面前還只有一個整型類型的成員——sched_priority,該成員表示線程的運行優先級。
  • schedpolicy:線程調度策略。該屬性有SCHED_FIFO、SCHED_RR和SCHED_OTHER三個可選值,其中SCHED_OTHER是默認值。SCHED_RR表示采用輪轉算法調度,SCHED_FIFO表示使用先進先出的方法調度,這兩種調度方法都具備實時調度功能,但只能用于以超級用戶身份運行的進程。
  • inheritsched:是否繼承調用線程的調度屬性。該屬性有PTHREAD_INHERIT_SCHED和PTHREAD_EXPLICIT_SCHED 兩個可選值。前者表示新線程沿用其創建者的線程調度參數,這種情況下再設置新線程的調度參數將沒有任何效果。后者表示調用者要明確的指定新線程的調度參數。
  • scope:線程間競爭CPU的范圍,即線程優先級的有效范圍。POSIX標準定義了該屬性的PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS兩個可選值,前者表示目標線程與系統中所有線程一起競爭CPU,后者表示目標線程僅與其他隸屬于同一進程的線程競爭CPU的使用。面前Linux 只支持PTHREAD_SCOPE_SYSTEM這一種取值。




接下來我們討論3種專門用于線程同步的機制:POSIX信號量,互斥量和條件變量
POSIX信號量
常用API#include <semaphore.h> int sem_init ( sem_t* sem, int pshared, unsigned int value ); // 初始化一個未命名的信號量 int sem_destroy ( sem_t* sem ); // 用于銷毀信號量,以釋放其占用的內核資源 int sem_wait ( sem_t*sem ); // 以原子操作的方式將信號量減1,如果信號量的值為0,則阻塞,直到該值不為0. int sem_trywait ( sem_t* sem ); // sem_wait的非阻塞版本 int sem_post ( sem_t* sem ); // 以原子操作的方式將信號量的值加1
  • sem_init函數用于初始化一個未命名的信號量。pshared參數指定信號量的類型。如果其值是0,就表示這個信號量是當前進程的局部信號量,否則該信號量就可以在多個進程之間共享。value參數指定信號量的初始值。此外,初始化一個已經被初始化的信號量將導致不可預期的結果。
  • sem_destroy函數用于銷毀信號量,以釋放其占用的內核資源。如果銷毀一個正被其他線程等待的信號量,則將導致不可預期的結果。
  • sem_wait函數以原子操作的方式將信號量的值減1.如果信號量的值為0,則sem_wait將被阻塞,直到這個信號量具有非0值。
  • sem_trywait與sem_wait函數相似,不過它始終立即返回,而不論被操作的信號量是否具有非0值,相當于sem_wait的非阻塞版本。當信號量的值大于0值時,sem_trywait對信號量執行減1操作。當信號量的值為0時,它將返回-1,并設置errno為EAGAIN。
  • sem_post函數以原子操作的方式將信號量的值加1.當信號量的值大于0時,其他正在調用sem_wait等待信號量的線程將被喚醒。
這些函數成功時返回0,失敗時返回-1并設置errno。


互斥鎖
作用:用于保護關鍵代碼段,以確保其獨占式訪問。基礎API:定義:#include <pthread.h> int pthread_mutex_init ( pthread_mutex_t* mutex, const pthread_mutexattr_t* mutexattr ); int pthread_mutex_destroy ( pthread_mutex_t* mutex ); int pthread_mutex_lock ( pthread_mutex_t* mutex ); int pthread_mutex_trylock ( pthread_mutex_t* mutex ); int pthread_mutex_unlock ( pthread_mutex_t* mutex );? ? ? ?pthread_mutex_init函數用于初始化互斥鎖。mutexattr參數指定互斥鎖的屬性。如果將它設置為NULL,則表示使用默認屬性。除了pthread_mutex_init函數,我們還可以如下初始化一個互斥鎖:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;宏PTHREAD_MUTEX_INITIALIZER實際上只是把互斥鎖的各個字段都初始化為0.? ? ? ?pthread_mutex_destroy函數用于銷毀互斥鎖,以釋放其占用的內核資源。銷毀一個已經加鎖的互斥鎖將導致不可預期的后果。? ? ? ?如果互斥鎖已經被加鎖,pthread_mutex_trylock將返回錯誤碼EBUSY。

互斥鎖屬性:pthread_mutexattr_t結構體中定義了一套完整的互斥鎖屬性這里我們列出其中一些主要的函數#include <pthread.h> /* 初始化互斥鎖屬性對象 */ int pthread_mutexattr_init ( pthread_mutexattr_t* attr ); /* 銷毀互斥鎖屬性對象 */ int pthread_mutexattr_destroy ( pthread_mutexattr_t* attr ); /* 獲取和設置互斥鎖的pshared屬性 */ int pthread_mutexattr_getpshared ( const pthread_mutexattr_t* attr, int * pshared ); int pthread_mutexattr_setpshared ( pthread_mutexattr_t* attr, int pthread ); /* 獲取和設置互斥鎖的type屬性 */ int pthread_mutexattr_gettype ( const pthread_mutexattr_t* atr, int * type ); int pthread_mutexattr_settype ( pthread_mutexattr_t* attr, int type );
這里提到了兩種常用屬性:pshared 和typepshared指定是否允許跨進程共享互斥鎖,其可選值有兩個:
  • PTHREAD_PROCESS_SHARED:互斥鎖可以被跨進程共享。
  • PTHREAD_PROCESS_PRIVATE:互斥鎖只能被和鎖的初始化線程隸屬于同一個進程的線程共享。
type指定互斥鎖的類型,Linux支持如下4種類型的互斥鎖:
  • PTHREAD_MUTEX_DEFAULT:默認鎖(缺省的互斥鎖類型屬性)。如果一個線程試圖對一個默認鎖重復鎖定或者試圖解鎖一個由別的線程鎖定的默認鎖或者試圖解鎖已經被解鎖的默認鎖會引發不可預料的結果。
  • PTHREAD_MUTEX_NORMAL普通鎖。當一個線程對一個普通鎖加鎖以后,其余請求該鎖的線程將形成一個等待隊列,并在該鎖解鎖后按優先級獲得鎖。這種鎖類型保證了資源分配的公平性。但這種鎖也很容易引發問題:(1)一個線程如果對一個已經加鎖的普通鎖再次加鎖,將引發死鎖。(2)對一個已經被其他線程加鎖的普通鎖解鎖,或者對一個已經解鎖的普通鎖再次解鎖,將導致不可預期的后果。
  • PTHREAD_MUTEX_ERRORCHECK檢錯鎖。?如果一個線程試圖對一個互斥鎖重復鎖定,將會返回一個錯誤碼EDEADLK。?如果試圖解鎖一個由別的線程鎖定的互斥鎖或者試圖解鎖已經被解鎖的互斥鎖,則解鎖操作返回EPERM。
  • PTHREAD_MUTEX_RECURSIVE嵌套鎖。如果一個線程對這種類型的互斥鎖重復上鎖,不會引起死鎖,不過一個線程對這類互斥鎖的多次重復上鎖必須由這個線程來重復相同數量的解鎖,這樣才能解開這個互斥鎖,別的線程才能得到這個互斥鎖。如果對一個已經被其他線程加鎖的嵌套鎖解鎖,或者對一個已經解鎖的嵌套鎖再次解鎖,則解鎖操作返回EPERM。這種類型的互斥鎖只能是進程私有的(作用域屬性為PTHREAD_PROCESS_PRIVATE)。



死鎖:結果:導致一個或多個線程被掛起而無法繼續執行原因:對一個已經加鎖的普通鎖再次枷鎖,將導致死鎖,這種情況可能出現在不夠仔細的遞歸函數中。如果兩個線程按照不同順序申請兩個互斥鎖,也容易產生死鎖。舉例:這段代碼或許總能成功的運行(不加入sleep函數刻意導致死鎖),但是會為程序留下一個潛在的bug。#include <pthread.h> #include <unistd.h> #include <stdio.h>int a = 0; int b = 0; pthread_mutex_t mutex_a; pthread_mutex_t mutex_b;void* another( void* arg ) {pthread_mutex_lock( &mutex_b );printf( "in child thread, got mutex b, waiting for mutex a\n" );sleep( 5 );++b;pthread_mutex_lock( &mutex_a );b += a++;pthread_mutex_unlock( &mutex_a );pthread_mutex_unlock( &mutex_b );pthread_exit( NULL ); }int main() {pthread_t id;pthread_mutex_init( &mutex_a, NULL );pthread_mutex_init( &mutex_b, NULL );pthread_create( &id, NULL, another, NULL );pthread_mutex_lock( &mutex_a );printf( "in parent thread, got mutex a, waiting for mutex b\n" );sleep( 5 );++a;pthread_mutex_lock( &mutex_b );a += b++; pthread_mutex_unlock( &mutex_b );pthread_mutex_unlock( &mutex_a );pthread_join( id, NULL );pthread_mutex_destroy( &mutex_a );pthread_mutex_destroy( &mutex_b );return 0; }

條件變量
互斥鎖的作用:用于同步線程對共享數據的訪問。條件變量:用于在線程之間同步同享數據的值。作用:提供了一種線程間通知的機制,當某個共享數據達到某個值的時候,喚醒等待這個共享數據的線程。相關函數:#include <pthread.h> int pthread_cond_init (pthread_cond_t* cond, const pthread_condattr_t* cond_attr); int pthread_cond_destroy ( pthread_cond_t* cond ); int pthread_cond_broadcast ( pthread_cond_t* cond ); //以廣播的形式喚醒一個等待目標條件變量的線程 int pthread_cond_signal ( pthread_cond_t* cond ); //喚醒一個等待目標條件變量的線程 int pthread_cond_wait ( pthread_cond_t* cond, pthread_mutex_t* mutex ); // 等待目標條件變量,mutex參數保證對條件變量及其等待隊列的操作原子性。
  • pthread_cond_init函數用于初始化條件變量。cond_attr參數指定條件變量的屬性。如果將它設置為NULL,則表示使用默認屬性。除了pthread_cond_init函數外,還可以如下初始化一個條件變量:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;宏PTHREAD_COND_INITIALIZER實際上只是把條件變量的各個字段都初始化為0.
  • pthread_cond_destroy 用于銷毀條件變量,以釋放其占用的內核資源。銷毀一個正在被等待的條件變量將失敗并返回EBUSY。
  • pthread_cond_broadcast 函數以廣播的方式喚醒所有等待目標條件變量的線程。
  • pthread_cond_signal 函數用于喚醒一個等待目標條件變量的線程。至于哪個線程將被喚醒,則取決于線程的優先級和調度策略。有時候我們可能想喚醒一個指定線程,但pthread沒有對該需求提供解決方案。不過我們可以間接實現該需求:定義一個能夠唯一表示目標線程的全局變量,在喚醒等待條件變量的線程前先設置該變量為目標線程,然后采用廣播方式喚醒所有等待條件變量的線程,這些線程被喚醒后都檢查該變量以判斷被喚醒的是否是自己,如果是就開始執行后續代碼,如果不是則返回繼續等待。
pthread_cond_wait 函數用于等待目標條件變量。mutex參數是用于保護條件變量的互斥鎖,以確保pthread_cond_wait 操作的原子性。在調用pthread_cond_wait 前,必須確保互斥鎖mutex已經加鎖,否則將導致不可預期的結果。pthread_cond_wait 函數執行時,首先把調用線程放入條件變量的等待隊列中,然后將互斥鎖mutex解鎖。 可見,從pthread_cond_wait 開始執行到其調用線程被放入條件變量的等待隊列之間的這段時間內,pthread_cond_signal 和pthread_cond_broadcast 等函數不會修改條件變量。換言之,pthread_cond_wait 函數不會錯過目標條件變量的任何變化。當pthread_cond_wait 函數成功返回時,互斥鎖mutex將再次被鎖上




對三種同步機制的封裝:
這是原書的代碼,我也沒有加注釋,上面的api都了解了,這里也不會有什么問題。#ifndef LOCKER_H #define LOCKER_H#include <exception> #include <pthread.h> #include <semaphore.h>class sem { public:sem(){if( sem_init( &m_sem, 0, 0 ) != 0 ){throw std::exception();}}~sem(){sem_destroy( &m_sem );}bool wait(){return sem_wait( &m_sem ) == 0;}bool post(){return sem_post( &m_sem ) == 0;}private:sem_t m_sem; };class locker { public:locker(){if( pthread_mutex_init( &m_mutex, NULL ) != 0 ){throw std::exception();}}~locker(){pthread_mutex_destroy( &m_mutex );}bool lock(){return pthread_mutex_lock( &m_mutex ) == 0;}bool unlock(){return pthread_mutex_unlock( &m_mutex ) == 0;}private:pthread_mutex_t m_mutex; };class cond { public:cond(){if( pthread_mutex_init( &m_mutex, NULL ) != 0 ){throw std::exception();}if ( pthread_cond_init( &m_cond, NULL ) != 0 ){pthread_mutex_destroy( &m_mutex );throw std::exception();}}~cond(){pthread_mutex_destroy( &m_mutex );pthread_cond_destroy( &m_cond );}bool wait(){int ret = 0;pthread_mutex_lock( &m_mutex );ret = pthread_cond_wait( &m_cond, &m_mutex );pthread_mutex_unlock( &m_mutex );return ret == 0;}bool signal(){return pthread_cond_signal( &m_cond ) == 0;}private:pthread_mutex_t m_mutex;pthread_cond_t m_cond; };#endif



線程和進程:
思考這樣一個問題:如果一個多線程程序的某個線程調用了fork函數,那么新創建的子進程是否將自動創建和父進程相同的數量的線程呢?答案是:“否”。子進程只擁有一個執行線程,它是調用fork的那個線程的完整復制。并且子進程將自動繼承父進程中互斥鎖(條件變量之類) 的狀態。也就是說,父進程中已經被加鎖的互斥鎖在子進程中也是被鎖住的。這就引起了一個問題:子進程可能不清楚從父進程繼承而來的互斥鎖的具體狀態(是加鎖狀態還是解鎖狀態)。這個互斥鎖可能被加鎖了,但并不是由調用fork函數的那個線程鎖住的,而是由其他線程鎖住的。如果是這種情況,則子進程若再次對該互斥鎖執行加鎖操作就會導致死鎖。
#include <pthread.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <wait.h>pthread_mutex_t mutex;/*子線程運行的函數。它首先獲得互斥鎖mutex,然后暫停5s ,再釋放該互斥鎖*/ void* another( void* arg ) {printf( "in child thread, lock the mutex\n" );pthread_mutex_lock( &mutex );sleep( 5 );pthread_mutex_unlock( &mutex ); }void prepare() {pthread_mutex_lock( &mutex ); }void infork() {pthread_mutex_unlock( &mutex ); }int main() {pthread_mutex_init( &mutex, NULL );pthread_t id;pthread_create( &id, NULL, another, NULL );//pthread_atfork( prepare, infork, infork );/*父進程中的主線程暫停1s,以確保在執行fork操作之前,子線程已經開始運行并獲得了互斥變量mutex*/sleep( 1 );int pid = fork();if( pid < 0 ){pthread_join( id, NULL );pthread_mutex_destroy( &mutex );return 1;}else if( pid == 0 ){printf( "I anm in the child, want to get the lock\n" );/*子進程從父進程繼承了互斥鎖mutex的狀態,該互斥鎖處于鎖住的狀態,這是由父進程中的子線程執行pthread_mutex_lock引起的,因此,下面這句加鎖操作會一直阻塞,盡管從邏輯上來說它是不應該阻塞的*/pthread_mutex_lock( &mutex );printf( "I can not run to here, oop...\n" );pthread_mutex_unlock( &mutex );exit( 0 );}else{pthread_mutex_unlock( &mutex );wait( NULL );}pthread_join( id, NULL );pthread_mutex_destroy( &mutex );return 0; }
不過,pthread提供了一個專門的函數pthread_atfork,以確保fork調用后父進程和子進程都擁有一個清楚的鎖狀態。該函數的定義如下:#include <pthread.h> int pthread_atfork( void (*prepare)(void), void (*parent)(void),void (*child)(void) );該函數將建立3個fork句柄來幫助我們清理互斥鎖的狀態。該函數成功時返回0,失敗則返回錯誤碼。
  • prepare 句柄將在fork調用創建出子進程之前被執行。它可以用來鎖住父進程中的互斥鎖。
  • parent 句柄則是fork調用創建出子進程之后,而fork返回之前,在父進程中被執行。它的作用是釋放所有在prepare 句柄中被鎖住的互斥鎖。
  • child 句柄是在fork 返回之前,在子進程中被執行。和parent句柄一樣,child 句柄也是用于釋放所有在prepare 句柄中被鎖住的互斥鎖。
使用pthread_atfork函數:
void prepare() {pthread_mutex_lock( &mutex ); }void infork() {pthread_mutex_unlock( &mutex ); }pthread_atfork( prepare, infork, infork );



線程和信號:
每個線程都可以獨立的設置信號掩碼。進程信號掩碼的函數sigpromask,但在多線程環境下我們應該使用如下所示的pthread 版本的sigpromask函數來設置線程信號掩碼:#include <pthread.h> include <signal.h> int pthread_sigmask( int how, const sigset_t* newmask, sigset_t* oldmask );由于進程中的所有線程共享該進程的信號,所以線程庫將根據線程掩碼決定把信號發送給哪個具體的線程。因此,如果我們在每個子線程中都單獨設置信號掩碼,就很容易導致邏輯錯誤。此外,所有線程共享信號處理函數。也就是說,當我們在一個線程中設置了某個信號的信號處理函數后,它將覆蓋其他線程為同一個信號設置的信號處理函數。這兩點都說明,我們應該定義一個專門的線程來處理所有的信號。這可以通過如下兩個步驟來實現:
  • 在主線程創建出其他子線程之前就調用pthread_sigmask來設置好信號掩碼,所有新創建的子線程都將自動繼承這個信號掩碼。這樣做之后,實際上所有線程都不會響應被屏蔽的信號了。
  • 在某個線程中調用如下函數來等待信號并處理之:#include <signal.h> int sigwait( const sigset_t* set, int* sig );set 參數指定需要等待的信號的集合。我們可以簡單的將其指定為在第一步中創建的信號掩碼,表示在該線程中等待所有被屏蔽的信號。參數sig 指向的整數用于存儲該函數返回的信號值。sigwait成功時返回0,失敗則返回錯誤碼。一旦sigwait正確返回,我們就可以對接收到的信號做處理了。很顯然,如果我們使用了sigwait,就不應該再為信號設置信號處理函數了。這是因為當程序接收到了信號時,二者中只能有一個起作用。
  • #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <errno.h>/* Simple error handling functions */#define handle_error_en(en, msg) \do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)static void *sig_thread(void *arg) {printf( "yyyyy, thread id is: %ld\n", pthread_self() );sigset_t *set = (sigset_t *) arg;int s, sig;for (;;) {/*第二步驟,調用sigwait等待信號*/s = sigwait(set, &sig);if (s != 0)handle_error_en(s, "sigwait");printf("Signal handling thread got signal %d\n", sig);} }int main(int argc, char *argv[]) {pthread_t thread;sigset_t set;int s;/*第一步驟,在主線程中設置信號掩碼*/sigemptyset(&set);sigaddset(&set, SIGQUIT);sigaddset(&set, SIGUSR1);s = pthread_sigmask(SIG_BLOCK, &set, NULL);if (s != 0)handle_error_en(s, "pthread_sigmask");s = pthread_create(&thread, NULL, &sig_thread, (void *) &set);if (s != 0)handle_error_en(s, "pthread_create");printf( "sub thread with id: %ld\n", thread );pause(); /* Dummy pause so we can test program */ }
    pthread 還提供了下面的方法,使得我們可以明確的將一個信號發送給指定的線程#include <signal.h> int pthread_kill( pthread_t thread, int sig );thread參數指定目標線程。sig參數指定待發送的信號。如果sig為0,則pthread_kill 不發送信號,但它仍然會執行錯誤檢查。我們可以利用這種方式來檢測目標線程是否存在。pthread_kill 成功返回0,失敗則返回錯誤碼。




    小結:這篇看似內容很多,其實大都是走馬觀花,了解一下整個框架和常用的API,起到一個入門和索引的作用,真正用到的時候,知道從哪里入手。這一本書的大概框架就被我們瀏覽完了(排除了我不太感興趣的幾個章節,如果需要的話,我們會回頭再來研究的。)關于服務器編程,目前來說只能算是興趣了解一下,并沒有太深入,這段時間事情比較多,打算看點別的方面的資料,后面的進度上可能會慢一點了。
    參考資料:《Linux高性能服務器編程》from:?http://blog.csdn.net/zs634134578/article/details/20706741

    轉載于:https://www.cnblogs.com/hehehaha/p/6332335.html

    總結

    以上是生活随笔為你收集整理的Linux 高性能服务器编程——多线程编程的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。