Linux 多线程编程

2023年 10月 15日 100.9k 0

线程标识

线程 ID 在应用程序中非常有用,原因如下。

  • 不同的 Pthreads 函数利用线程 ID 来标识要操作的目标线程。这些函数包括 pthread_join()、pthread_detach()、pthread_cancel()和 pthread_kill()等
  • 在一些应用程序中,以特定线程的线程 ID 作为动态数据结构的标签,这颇有用处, 既可用来识别某个数据结构的创建者或属主线程,又可以确定随后对该数据结构执行
/*
返回值:
    若相等,返回 非0值
    否则 返回 0;
*/
#include 

int pthread_equal(pthread_t tid1, pthread_t tid2);
/* 线程id只有在它所属的进程上下文环境中才有意义
    线程id用 pthread_t 数据类型来标识,实现是时候可以用一个结构来代表,
    所以在可移植的系统实现上不能把它作为一个整数来处理
*/

//返回值:返回调用线程的线程ID
pthread_t pthread_self(void); 

POSIX 线程 ID 与 Linux 专有的系统调用 gettid() 所返回的线程 ID 并不相同。POSIX 线程 ID 由线程库实现来负责分配和维护。gettid()返回的线程 ID 是一个由内核(Kernel)分配 的数字,类似于进程 ID(process ID)

创建线程

线程创建并不保证哪个线程先执行,新创建的线程可以访问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该线程的挂起信号集会被清除

/*
返回值 :
    若成功返回 0;
    否则 ,返回错误编号
*/
#include 

int pthread_create(pthread_t * tidp,const pthread_attr_t *  attr,
                    void *  (* start_rtn)(void * ),void * arg);

/* Value returned from pthread_join() when a thread is canceled */

#define PTHREAD_CANCELED ((void *) -1)
  • 参数 tidp : 当 pthread_create 成功返回时,tidp 指向新创建线程的线程ID的内存单元
  • 参数 attr : 配置线程属性,一般设置为 NULL ,创建默认属性的线程
  • 参数 star_rtn :
    • 新创建的线程从start_rtn函数的起始地址开始执行star_rtn()的返回值类型为 void*,对其使用方式与参数 arg 相同。对后续 pthread_join()函数的描述中,将论及对该返回值的使用方式。将经强制转换的整型数作为线程 start 函数的返回值时,必须小心谨慎。
    • 原因在于,取消线程时的返回值PTHREAD_CANCELED,通常是由实现所定义的整型值,再经强制转换为 void*。若线程某甲的 star_rtn 函数将此整型值返回给正在执行 pthread_join()操作的线程某乙,某乙会误认为某甲遭到了取消。应用如果采用了线程取消技术并选择将 star_rtn 函数的返回值强制转换为整型,那么就必须确保线程正常结束时的返回值与当前 Pthreads 实现中的 PTHREAD_CANCELED 不同
  • 参数 arg: 是 satr_rtn 函数的参数,如果 start_rtn 传递的参数有一个以上,那么需要将这些参数放在结构体当中。将参数 arg 声明为 void* 类型,意味着可以将指向任意对象的指针传递给 start()函数。一般情况下,arg 指向一个全局或堆变量,也可将其置为 NULL。通过审慎的类型强制转换,arg 甚至可以传递 int 类型的值。

线程属性

pthread_create()中类型为 pthread_attr_t 的 attr 参数,可利用其在创建线程时
指定新线程的属性.

初始化线程属性

/*
两个函数的返回值 :
    若成功,返回 0
    否则 返回错误编号
*/

#include 

int pthread_attr_init(pthread_attr_t * attr); // attr 初始化默认的属性对象

int pthread_attr_destroy(pthread_attr_t * attr); //释放 attr

线程属性之线程分离


#define PTHREAD_CREATE_JOINABLE      1  // 线程不分离 默认状态
#define PTHREAD_CREATE_DETACHED      2  //线程分离

/*
两个函数的返回值 :
    若成功,返回 0
    否则 返回错误编号
*/

// detachstate取值为上面两个宏的值,为 attr线程属性进行配置

#include 

int pthread_attr_getdetachstate(const pthread_attr_t * attr, int * detachstate);

int pthread_attr_setdetachstate(pthread_attr_t * attr, int  detachstate);

线程属性之线程栈

对应进程来说,虚拟地址的空间大小是固定的,因为进程只有一个栈,所以进程的栈的大小通常不是问题.

但对应线程来说,同样大小的虚拟地址空间必须被所有的线程栈共享.如果线程栈的虚拟地址空间都用完了
可以使用 malloc 或者 mmap 来为可替代的栈分配空间

pthread_attr_setstack 函数来改变新建线程的栈位置, stackaddr 参数指定的地址可以用作线程栈的内存范围中的最低可寻址地址(但不一定是栈的开始位置,栈地址增长方向不一样,可能是结尾位置) ,stacksize指定栈的大小


/*
两个函数的返回值 :
    若成功,返回 0
    否则 返回错误编号
*/
#include 

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);

线程属性之线程栈大小 stacksize

如果希望改变默认栈的大小,但又不想自己处理线程栈的分配问题可以使用 pthread_attr_setstacksize函数 指定 stacksize,不能小于 PTHREAD_STACK_MIN


/*
两个函数的返回值 :
    若成功,返回 0
    否则 返回错误编号
*/
#include 

int pthread_attr_getstacksize(const pthread_attr_t * restrict attr, 
                                size_t * restrict stacksize);

int pthread_attr_setstacksize(pthread_attr_t * attr, size_t stacksize);

线程属性之 guardsize

控制着线程末尾之后可用以避免栈溢出的扩展内存的大小,常用值是系统页大小,

可以把guardsize设置为0,在这种情况下,不会提供警戒缓冲区.

如果线程修改了 stackaddr属性, 系统认为我们将自己管理栈,进而使栈的警戒缓冲区机制无效

/*
两个函数的返回值 :
    若成功,返回 0
    否则 返回错误编号
*/
#include 

int pthread_attr_getguardsize(const pthread_attr_t *  attr, 
                                    size_t *  guardsize);

int pthread_attr_setguardsize(pthread_attr_t * attr, size_t guardsize);

线程终止

可以如下方式终止线程的运行。

  • 线程 satr_rtn 函数执行 return 语句并返回指定值。
  • 线程调用 pthread_exit() 。(只能作用于当前线程自身调用,终止当前线程)
  • 调用 pthread_cancel() 取消线程。(给其它线程发送请求,要求其立即退出)
  • 任意线程调用了 exit(),或者主线程执行了 return 语句(在 main()函数中) ,都会导致进程中的所有线程立即终止。
#include 

 // 终止线程,如果对线程终止状态不感兴趣,可为NULL
void pthread_exit(void * rval_ptr);

调用 pthread_exit()相当于在线程的 start_rtn 函数中执行 return,不同之处在于,
可在线程 start_rtn 函数所调用的任意函数中调用 pthread_exit() 。

  • 参数 rval_ptr 指定了线程的返回值。
    • rval_ptr 所指向的内容不应分配于线程栈中,因为线程终止后, 将无法确定线程栈的内容是否有效。(例如,系统可能会立刻将该进程虚拟内存的这片区域重新分 配供一个新的线程栈使用。)出于同样的理由,也不应在线程栈中分配线程 satr_rtn 函数的返回值。
    • 如果主线程调用了 pthread_exit(),而非调用 exit()或是执行 return 语句,那么其他线程将 继续运行。

线程取消

函数 pthread_cancel()向由 tid 指定的线程发送一个取消请求。发出取消请求后,函数 pthread_cancel() 当即返回,不会等待目标线程的退出

#include 

// 成功返回 0 ,失败 返回一个错误码
int pthread_cancel (pthread_t tid);

当目标线程接收到取消请求时,目标线程的动作取决于目标线程的取消状态和取消类型

  • 可取消状态
#define PTHREAD_CANCEL_ENABLE        0x01  /* Cancel takes place at next cancellation point */
#define PTHREAD_CANCEL_DISABLE       0x00  /* Cancel postponed */
/*
返回值 :
    若成功,返回 0
    否则 返回错误编号
*/
#include 

// 设置新取消状态 state 和存储旧取消状态 oldstate 是原子操作
int pthread_setcancelstate(int state, int * oldstate); 
/*
  - PTHREAD_CANCEL_ENABLE 默认可取消状态,
         目标线程收到取消请求后还是继续运行,直到线程达到取消点.
        取消点是线程检查它是否被取消的一个位置(调用特定的函数才会检查),目标线程才会被取消.
 -  PTHREAD_CANCEL_DISABLE :在此取消状态情况下,对pthread_cancel 的调用并不会杀死线程,
        相反,取消请求的这个线程来说还处于挂起的状态,当取消状态再次变为PTHREAD_CANCEL_ENABLE时,
        线程将在下一个取消点对挂起的取消请求进行处理
*/

#define PTHREAD_CANCEL_DEFERRED      0x02  /* Cancel waits until cancellation point */
#define PTHREAD_CANCEL_ASYNCHRONOUS  0x00  /* Cancel occurs immediately */

/*
返回值 :
    若成功,返回 0
    否则 返回错误编号
*/
#include 

int pthread_setcanceltype(int type, int * oldtype);
/*
    PTHREAD_CANCEL_DEFERRED 默认取消类型,只有到达取消点才会取消线程
    PTHREAD_CANCEL_ASYNCHRONOUS 可能会在任何时点(也许是立即取消,但不一定)取消线程
*/

SUSv3 规定,实现若提供了下表中所列的函数,则这些函数必须是取消点。其中的大部分函数都有能力将线程无限期地堵塞起来。
2023-09-26_07-24

假设线程执行的是一个不含取消点的循环(计算密集型[compute-bound]循环),这时,线程永远也不会响应取消请求。函数 pthread_testcancel()的目的很简单,就是产生一个取消点。线程如果已有处于挂起状态的取消请求,那么只要调用该函数,线程就会随之终止。

    void pthread_testcancel(void) ;
    /*
        调用pthread_testcancel 时,如果有某个取消请求正处于挂起状态,而且取消并没有置为无效,那么线程就会被取消
        但是如果取消被置为无效,pthread_testcancel调用无任何效果
    */

清理函数

一旦有处于挂起状态的取消请求,线程在执行到取消点时如果只是草草收场,这会将共享
变量以及 Pthreads 对象(例如互斥量)置于一种不一致状态,可能导致进程中其他线程产生错
误结果、死锁,甚至造成程序崩溃。为规避这一问题,线程可以设置一个或多个清理函数,当线
程遭取消时会自动运行这些函数,在线程终止之前可执行诸如修改全局变量,解锁互斥量等动作。

每个线程都可以拥有一个清理函数栈。当线程遭取消时,会沿该栈自顶向下依次执行清
理函数,首先会执行最近设置的函数,接着是次新的函数,以此类推。当执行完所有清理函
数后,线程终止。

//线程清除处理程序 类似 进程退出的 atexit 函数, 如果进程是通过 return 而终止线程的话,线程处理程序不会被调用

void pthread_cleanup_push(void (*rtn) (void *),void * arg); //添加线程清理程序
/*
    响应取消线程请求时,调用添加的线程处理程序,添加顺序与调用顺序相反
    rtn: 函数指针, arg 传递给 rtn 函数的参数
*/

void pthread_cleanup_pop(int execute);//删除线程清理函数 
/*
    如果execute为 0,清理函数不会被调用.
    不管哪种情况下,pthread_cleanup_pop都将删除上次pthread_cleanup_push调用建立的清理处理程序
*/

线程连接分离

函数 pthread_join()等待由 thread 标识的线程终止。(如果线程已经终止,pthread_join()会立即返回)。这种操作被称为连接(joining)。

如向 pthread_join()传入一个之前已然连接过的线程 ID,将会导致无法预知的行为。例如,相 同的线程 ID 在参与一次连接后恰好为另一新建线程所重用,再度连接的可能就是这个新线程。

若线程并未分离(detached),则必须使用 ptherad_join()来进行连接。如果未能连接,那么线程终止时将产生僵尸线程,与僵尸进程(zombie process)的概念相类似。除了浪费系统资源以外,僵尸线程若累积过多,应用将再也无法创建新的线程。

/*
返回值 :
    若成功,返回 0
    否则 返回错误编号
*/
int pthread_join(pthread_t thread, void ** rval_ptr); 
/*
    可调用pthread_join将线程一直阻塞,直到指定的线程调用pthread_exit,从启动例程返回或者被取消。
    此方式会自动把线程置于分离状态,这样资源就可以恢复,
    如果线程已经处于分离状态,pthread_join会调用失败,返回EINVAL
    若 rval_ptr 为一非空指针,将会保存线程终止时返回值的拷贝,
         该返回值亦即线程调用 return 或pthred_exit()时所指定的值。
    若等待的线程pthread_exit参数设置为NULL,pthread_join获取不到线程终止状态
*/

默认情况下,线程是可连接的(joinable),也就是说,当线程退出时,其他线程可以通过调
用 pthread_join()获取其返回状态。有时,程序员并不关心线程的返回状态,只是希望系统在
线程终止时能够自动清理并移除之。在这种情况下,可以调用 pthread_detach()并向 thread 参数传入指定线程的标识符,将该线程标记为处于分离(detached)状态

/*
返回值 :
    若成功,返回 0
    否则 返回错误编号
*/
int pthread_detach(pthread_t tid); 
/*
    在默认情况下,线程的终止状态会保存直到对线程调用 pthread_join 为止
    如果tid线程调用了pthread_detach使线程分离,线程的底层存储资源可以在线程终止时立即被收回.
    线程被分离之后,不能调用pthread_join等待线程 tid 的终止状态

*/

如下例所示,使用 pthread_detach(),线程可以自行分离:

pthread_detach(pthread_self());

一旦线程处于分离状态,就不能再使用 pthread_join()来获取其状态,也无法使其重返“可
连接”状态。
其他线程调用了 exit(),或是主线程执行 return 语句时,即便遭到分离的线程也还是会受
到影响。此时,不管线程处于可连接状态还是已分离状态,进程的所有线程会立即终止。pthread_detach()只是控制线程终止之后所发生的事情,而非何时或如何终止线程。

线程同步

线程的主要优势在于,能够通过全局变量来共享信息。不过,这种便捷的共享是有代价的。必须确保多个线程不会同时修改同一变量,或者某一线程不会读取正由其他线程修改的变量

  • 互斥量

为避免线程更新共享变量时所出现问题,必须使用互斥量(mutex 是 mutual exclusion 的
缩写)来确保同时仅有一个线程可以访问某项共享资源。

互斥量有两种状态:已锁定(locked)和未锁定(unlocked)。任何时候,至多只有一个线程可以锁定该互斥量。试图对已经锁定的某一互斥量再次加锁,将可能阻塞线程或者报错失
败,一旦线程锁定互斥量,随即成为该互斥量的所有者。只有所有者才能给互斥量解锁

互斥变量 pthread_mutex_t 初始化
  • 可以用pthread_mutex_init函数动态分配,但必须得用pthread_mutex_destroy函数释放内存
  • 也可以使用PTHREAD_MUTEX_INITIALIZER宏静态分配
  • #define PTHREAD_MUTEX_INITIALIZER {_PTHREAD_MUTEX_SIG_init, {0}}
    
    #include 
    /*
    两个函数的返回值 :
        若成功,返回 0
        否则 返回错误编号
    */
    int pthread_mutex_init(pthread_mutex_t * mutex,
                const pthread_mutexattr_t *  attr); // 使用默认的属性, attr参数可设置为 NULL
    
    int pthread_mutex_destroy(pthread_mutex_t * mutex);
    
    互斥量属性

    互斥量进行初始化时,都是通过使用 PTHREAD_MUTEX_INITIALIZER 常量或者用指向互斥量属性结构的空指针作为参数调用pthread_mutex_init函数,得到互斥量的默认属性。

    对于非默认属性,可以用pthread_mutexattr_init初始化pthread_mutexattr_t结构,用pthread_mutexattr_destroy 来释放

    /*
    两个函数的返回值 :
        若成功,返回 0
        否则 返回错误编号
    */
    #include 
    
    int pthread_mutexattr_init(pthread_mutexattr_t *);
    
    int pthread_mutexattr_destroy(pthread_mutexattr_t *) ;
    
    互斥量进程共享属性

    就像多个线程访问共享数据一样,多个进程访问共享数据通常也需要同步。如果进程共享互斥量属性设置为PTHREAD_PROCESS_SHARED,从多个进程彼此之间共享的内存数据块中分配的互斥量就可以用于这些进程的同步

    /* Process shared or private flag.  */
    enum
    {
      PTHREAD_PROCESS_PRIVATE,
    #define PTHREAD_PROCESS_PRIVATE PTHREAD_PROCESS_PRIVATE
      PTHREAD_PROCESS_SHARED
    #define PTHREAD_PROCESS_SHARED  PTHREAD_PROCESS_SHARED
    };
    
    /*
    两个函数的返回值 :
        若成功,返回 0
        否则 返回错误编号
    */
    #include 
    
    int pthread_mutexattr_getpshared(const pthread_mutexattr_t * attr,
    		int * pshared);
    		
    int pthread_mutexattr_setpshared(pthread_mutexattr_t * attr, int shared);
    
    互斥量健壮属性

    互斥量健壮属性与多进程间共享互斥量有关.当持有互斥量的进程终止时,需要解决互斥量状态恢复问题.

  • PTHREAD_MUTEX_STALLED, 默认值,意味着持有互斥量的进程终止时不需要采取特别的动作,等待互斥量解锁的进程会有效的拖住,一直阻塞下去
  • PTHREAD_MUTEX_ROBUST ,当持有互斥量锁的进程终止时,pthread_mutex_lock的线程会阻塞,但会返回 EOWNERDEAD 如果应用状态无法恢复,在线程对互斥量解锁以后,该互斥量处于永久不可以状态
  • 为避免这样的问题,线程可以调用 pthread_mutex_consistent ,指明该互斥量相关的状态在互斥量解锁之前是一致的.如果线程没有先调用pthread_mutex_consistent就对互斥量进行了解锁,那么其他试图获取该互斥量的阻塞线程就会得到错误码 ENOTRECOVERABLE
  • /* Robust mutex or not flags.  */
    enum
    {
      PTHREAD_MUTEX_STALLED
      PTHREAD_MUTEX_STALLED_NP = PTHREAD_MUTEX_STALLED,
      PTHREAD_MUTEX_ROBUST,
      PTHREAD_MUTEX_ROBUST_NP = PTHREAD_MUTEX_ROBUST
    };
    
    /* for robust mutexes */
    #define	EOWNERDEAD	130	/* Owner died */
    #define	ENOTRECOVERABLE	131	/* State not recoverable */
    /*
    两个函数的返回值 :
        若成功,返回 0
        否则 返回错误编号
    */
    #include 
    
    /* Get the robustness flag of the mutex attribute ATTR.  */
    int pthread_mutexattr_getrobust (const pthread_mutexattr_t *attr,
    					int * restrict robustness);
    					
    /* Set the robustness flag of the mutex attribute ATTR.  */
    int pthread_mutexattr_setrobust (pthread_mutexattr_t * attr,
    					int robustness)
    					
    /* Declare the state protected by MUTEX as consistent.  */
    int pthread_mutex_consistent (pthread_mutex_t * mutex);
    
    互斥量类型属性
    互斥量类型 没有解锁时重新加锁 不占时解锁 在已解锁是解锁
    PTHREAD_MUTEX_NORMAL 死锁 未定义 未定义
    PTHREAD_MUTEX_ERRORCHECK 返回错误 返回错误 返回错误
    PTHREAD_MUTEX_RECURSIVE 允许 返回错误 返回错误
    PTHREAD_MUTEX_DEFAULT 未定义 未定义 未定义
    /*
     * Mutex type attributes
     */
    #define PTHREAD_MUTEX_NORMAL	    0  //标准类型,不做任何特殊的错误检查和死锁检测
    #define PTHREAD_MUTEX_ERRORCHECK    1  // 提供错误检测
    #define PTHREAD_MUTEX_RECURSIVE	    2  // 递归锁,获取了锁的线程可再次加锁,释放锁要加锁次数相同才能释放
    #define PTHREAD_MUTEX_DEFAULT	    PTHREAD_MUTEX_NORMAL // 各个unix系统进行映射各有不同
    
    /*
    返回值 :
        若成功,返回 0
        否则 返回错误编号
    */
    #include 
    
    int pthread_mutexattr_gettype(const pthread_mutexattr_t * restrict attr,
    		int * restrict type);
    
    int pthread_mutexattr_settype(pthread_mutexattr_t * attr, int type);
    
    互斥量加锁和解锁
    /*
    四个函数的返回值 :
        若成功,返回 0
        否则 返回错误编号
    */
    #include 
    
    int pthread_mutex_lock(pthread_mutex_t * mutex); //加锁
    
    int pthread_mutex_trylock(pthread_mutex_t * mutex);
    /*
        (1) 如果调用pthread_mutex_trylock时互斥量处于未锁住的状态,
            那么pthread_mutex_trylock会将锁住互斥量,不会出现阻塞,函数返回 0
        (2) 否则,pthread_mutex_trylock就会失败,不能锁住互斥量,返回 EBUSY
    */
    
    
    int pthread_mutex_timedlock (pthread_mutex_t * restrict mutex,
    				    const struct timespec * restrict tsptr);
    /*
        超过指定愿意等待的绝对时间tsptr无法获取到锁,
        pthread_mutex_timedlock 不会对互斥量加锁进行阻塞,而是返回 ETIMEDOUT
    */
    
    
    int pthread_mutex_unlock(pthread_mutex_t * mutex);//解锁
    
    • 读写锁

    读写锁与互斥量类似,不过读写锁允许更高的并发量. 线程间 读写互斥,写写互斥,但读读不互斥 读写线程排队获取锁的过程,排队的写锁通常会先获取到锁,这样可以避免读模式锁长期占用

    读写锁 pthread_rwlock_t 初始化
  • 可以用 pthread_rwlock_init 函数动态分配,但必须得用 pthread_rwlock_destroy 函数释放内存
  • 也可以使用 PTHREAD_RWLOCK_INITIALIZER 宏静态分配
  • #define PTHREAD_RWLOCK_INITIALIZER {_PTHREAD_RWLOCK_SIG_init, {0}}
    /*
    两个函数的返回值 :
        若成功,返回 0
        否则 返回错误编号
    */
    #include 
    
    int pthread_rwlock_init(pthread_rwlock_t * restrict rwlock,
    		const pthread_rwlockattr_t * attr);// 使用默认的属性, attr参数可设置为 NULL
    		
    int pthread_rwlock_destroy(pthread_rwlock_t * rwlock);
    
    读写锁属性

    pthread_rwlockattr_t 读写锁属性对象

    /*
    两个函数的返回值 :
        若成功,返回 0
        否则 返回错误编号
    */
    #include 
    
    int pthread_rwlockattr_init(pthread_rwlockattr_t * attr);
    int pthread_rwlockattr_destroy(pthread_rwlockattr_t * attr);
    

    读写锁支持一种属性就是 读写锁 进程共享属性

    /*
    两个函数的返回值 :
        若成功,返回 0
        否则 返回错误编号
    */
    #include 
    
    int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t * restrict attr,
    		int * restrict pshard);
    int pthread_rwlockattr_setpshared(pthread_rwlockattr_t * attr, int pshard);
    
    读写锁加锁和解锁
    /*
    七个函数的返回值 :
        若成功,返回 0
        否则 返回错误编号
    */
    #include 
    
    int pthread_rwlock_rdlock(pthread_rwlock_t * rwlock); // 加读锁
    int pthread_rwlock_wrlock(pthread_rwlock_t * rwlock); // 加写锁
    
    
    
    int pthread_rwlock_tryrdlock(pthread_rwlock_t * rwlock);
    int pthread_rwlock_trywrlock(pthread_rwlock_t * rwlock);
    
    int pthread_rwlock_timedrdlock (pthread_rwlock_t * restrict rwlock,
    				       const struct timespec * restrict tsptr) ;
    int pthread_rwlock_timedwrlock (pthread_rwlock_t *restrict rwlock,
    				       const struct timespec *restrict tsptr);
                                           
    int pthread_rwlock_unlock(pthread_rwlock_t * rwlock); //读写锁都可以用此函数释放锁                                     
    
    • 自旋锁

    自旋锁在获取不到锁的时候,不会像互斥量一样进行休眠,而会一直占着 CPU 进行忙等

    自旋锁 pthread_spinlock_t 初始化
    /* Process shared or private flag.  */
    enum
    {
      PTHREAD_PROCESS_PRIVATE,
    #define PTHREAD_PROCESS_PRIVATE PTHREAD_PROCESS_PRIVATE
      PTHREAD_PROCESS_SHARED
    #define PTHREAD_PROCESS_SHARED  PTHREAD_PROCESS_SHARED
    };
    
    /*
    两个函数的返回值 :
        若成功,返回 0
        否则 返回错误编号
    */
    #include 
    
    /* Initialize the spinlock LOCK.  If PSHARED is nonzero the spinlock can
       be shared between different processes.  */
     int pthread_spin_init (pthread_spinlock_t *lock, int pshared);
     /*
        自旋锁只有pshared一个属性,表示进程共享属性
        (1) PTHREAD_PROCESS_PRIVATE 表示进程私有的,只有进程内部线程才能访问
        (2) PTHREAD_PROCESS_SHARED  则自旋锁可被访问锁底层内存的线程所获取.即使那些线程属于不同的进程.
     */
    
    /* Destroy the spinlock LOCK.  */
    int pthread_spin_destroy (pthread_spinlock_t * lock); //释放自旋锁
    
    自旋锁加锁和解锁
    /*
    三个个函数的返回值 :
        若成功,返回 0
        否则 返回错误编号
    */
    #include 
    /* Wait until spinlock LOCK is retrieved.  */
    int pthread_spin_lock (pthread_spinlock_t *lock);//如果不能获取到锁会自旋
    
    /* Try to lock spinlock LOCK.  */
    int pthread_spin_trylock (pthread_spinlock_t * lock);//尝试获取锁,如果不能获取到锁不会自旋,返回 EBUSY
    
    /* Release spinlock LOCK.  */
    int pthread_spin_unlock (pthread_spinlock_t * lock) // 自旋锁解锁
    
    
    • 屏障

    允许任意数量的线程等待,直到所有合作线程都达到某个点,然后 所有线程从该点继续执行

    屏障 pthread_barrier_t 初始化
    /*
    两个函数的返回值 :
        若成功,返回 0
        否则 返回错误编号
    */
    #include 
    
    
    int pthread_barrier_init (pthread_barrier_t *  barrier,const pthread_barrierattr_t * attr, unsigned int count); // 初始化
    /*
      - barrier 需要初始化的 pthread_barrier_t 变量的指针
      - attr 屏障属性,设置为 NULL ,可以用默认属性初始化
      - count : 在允许所有线程继续执行之前,必须达到屏障的线程数两
    */
    
    int pthread_barrier_destroy (pthread_barrier_t *barrier); // 释放
    
    屏障等待
    # define PTHREAD_BARRIER_SERIAL_THREAD -1
    /*
    返回值 :
        若成功,返回 0 或者 PTHREAD_BARRIER_SERIAL_THREAD
        否则 返回错误编号
    */
    #include 
    
    /* Wait on barrier BARRIER.  */
    int pthread_barrier_wait (pthread_barrier_t *barrier);
    /*
      若调用 pthread_barrier_wait 的线程的数量未达到 barrier初始化时指定的 count 数,则会被阻塞。
      如果该线程是最后一个调用 pthread_barrier_wait的线程,所有等待的线程都会被唤醒。 
      一旦达到屏障的计数值,而且线程处于非阻塞状态,屏障就可以重用
    */
    

    pthread_barrierattr_t 屏障属性对象

    /*
    两个函数的返回值 :
        若成功,返回 0
        否则 返回错误编号
    */
    #include 
    
    /* Initialize barrier attribute ATTR.  */
    int pthread_barrierattr_init (pthread_barrierattr_t * attr);
    
    /* Destroy previously dynamically initialized barrier attribute ATTR.  */
    int pthread_barrierattr_destroy (pthread_barrierattr_t * attr);
    
    
    • 屏障支持一种属性就是 屏障 进程共享属性
    /*
    两个函数的返回值 :
        若成功,返回 0
        否则 返回错误编号
    */
    #include 
    
    /* Get the process-shared flag of the barrier attribute ATTR.  */
    extern int pthread_barrierattr_getpshared (const pthread_barrierattr_t *restrict attr,
                                        int * restrict  pshared);
    
    /* Set the process-shared flag of the barrier attribute ATTR.  */
    extern int pthread_barrierattr_setpshared (pthread_barrierattr_t * attr,
    					   int pshared)
    
    • 条件变量

    条件变量允许一个线程就某个共享变量(或其他共享资源)的状态变化通知其他线程,并让其他线程等待(堵塞于)这一通知。条件变量是由互斥变量保护的,线程在改变条件状态之前必须先获取到互斥量锁

    条件变量 pthread_cond_t 初始化
  • 可以用pthread_cond_init函数动态分配,但必须得用pthread_cond_destroy函数释放内存
  • 也可以使用PTHREAD_COND_INITIALIZER宏静态分配
  • 
    #define PTHREAD_COND_INITIALIZER {_PTHREAD_COND_SIG_init, {0}}
    /*
    两个个函数的返回值 :
        若成功,返回 0
        否则 返回错误编号
    */
    #include 
    
    int pthread_cond_init(pthread_cond_t * restrict cond,
    		const pthread_condattr_t * restrict attr);// 使用默认的属性, attr参数可设置为 NULL
    int pthread_cond_destroy(pthread_cond_t * cond); // 释放条件变量
    
    条件变量属性

    pthread_condattr_t 条件变量属性对象

    /*
    两个函数的返回值 :
        若成功,返回 0
        否则 返回错误编号
    */
    #include 
    
    int pthread_condattr_init(pthread_condattr_t * attr);
    
    int pthread_condattr_destroy(pthread_condattr_t * attr);
    

    条件变量 进程共享属性

    /*
    两个函数的返回值 :
        若成功,返回 0
        否则 返回错误编号
    */
    #include 
    
    int pthread_condattr_getpshared(const pthread_condattr_t * restrict attr,
    		int * restrict pshared);
    
    int pthread_condattr_setpshared(pthread_condattr_t * attr, int pshared);
    

    条件变量 时钟属性
    时钟属性用来控制计算 pthread_cond_timedwait 函数的超时参数 tsptr采用的是哪个时钟

    /* Get the clock selected for the condition variable attribute ATTR.  */
    int pthread_condattr_getclock (const pthread_condattr_t * attr,
    				      clockid_t * clock_id);
    
    /* Set the clock selected for the condition variable attribute ATTR.  */
    extern int pthread_condattr_setclock (pthread_condattr_t * attr,
    				      clockid_t clock_id);
    
    条件变量 pthread_cond_t 等待通知

    条件变量的主要操作是发送信号(signal)和等待(wait)

    /*
    四个函数的返回值 :
        若成功,返回 0
        否则 返回错误编号
    */
    #include 
    
    int pthread_cond_wait(pthread_cond_t *  cond,
    		pthread_mutex_t * mutex); // 释放互斥量并且等待唤醒,
    int pthread_cond_timedwait(pthread_cond_t *  cond, pthread_mutex_t * mutex,const struct timespec * tsptr); // 等待超时报 ETIMEOUT 错误
    
    int pthread_cond_signal(pthread_cond_t * cond); // 只保证唤醒至少一条遭到阻塞
    的线程
    int pthread_cond_broadcast(pthread_cond_t * cond); // 会唤醒所有遭阻塞的线程
    

    互斥量的释放与陷入对条件变量的等待同属于一个原子操作

    一般使用

    
    pthread_mutex_lock(&mtx);
    while (avail == 0) {         
        // 当条件不满足时释放互斥量并且等待唤醒,
        pthread_cond_wait(&cond, &mtx);
    }
    while (avail > 0) {             
        avail--;   // 操作 avail 共享变量置于互斥量保护之下
    }
     pthread_mutex_unlock(&mtx);
    
    pthread_mutex_lock(&mtx);
    avail++;     // 操作 avail 共享变量置于互斥量保护之下
    pthread_mutex_unlock(&mtx); //先解锁
    pthread_cond_signal(&cond); //再释放
    

    每个条件变量都有与之相关的判断条件,涉及一个或多个共享变量,必须由一个 while
    循环,而不是 if 语句,来控制对 pthread_cond_wait()的调用。这是因为,当代码从 pthread_cond_wait()返回时,并不能确定判断条件的状态,所以应该立即重新检查判断条件,在条件不满足的情况下继续休眠等待。

    从 pthread_cond_wait()返回时,之所以不能对判断条件的状态做任何假设,其理由如下。

    • 其他线程可能会率先醒来。也许有多个线程在等待获取与条件变量相关的互斥量。即
      使就互斥量发出通知的线程将判断条件置为预期状态,其他线程依然有可能率先获取互
      斥量并改变相关共享变量的状态,进而改变判断条件的状态。

    • 设计时设置“宽松的”判断条件或许更为简单。有时,用条件变量来表征可能性而非
      确定性,在设计应用程序时会更为简单。换言之,就条件变量发送信号意味着“可能有
      些事情”需要接收信号的线程去响应,而不是“一定有一些事情”要做。使用这种方
      法,可以基于判断条件的近似情况来发送条件变量通知,接收信号的线程可以通过再
      次检查判断条件来确定是否真的需要做些什么。

    • 可能会发生虚假唤醒的情况。在一些实现中,即使没有任何其他线程真地就条件变量
      发出信号,等待此条件变量的线程仍有可能醒来。在一些多处理器系统上,为确保高效
      实现而采用的技术会导致此类(不常见的)虚假唤醒

    • 线程一次性初始化

    多线程程序有时有这样的需求:不管创建了多少线程,有些初始化动作只能发生一次。无论首次为任何线程所调用,都会执行初始化动作

    pthread_once_t : 变量使用 PTHREAD_ONCE_INIT 进行初始化

    #define PTHREAD_ONCE_INIT 0
    
    pthread_once_t initflag = PTHREAD_ONCE_INIT;
    /*
    返回值 :
        若成功,返回 0
        否则 返回错误编号
    */
    int pthread_once(pthread_once_t * initflag, void (* initfn)(void)); 
    /*
        initflag必须是一个非本地变量(如全局变量,或者静态变量),而且必须初始化为 PTHREAD_ONCE_INIT
    */ 
    

    利 用 参 数 initflag 的 状 态 , 函 数 pthread_once() 可 以 确 保 无 论 有 多 少 线 程 对pthread_once()调用了多少次,也只会执行一次由 initfn 指向的调用者定义函数。

    • 线程特有数据

    要使用线程特有数据,库函数执行的一般步骤如下。

  • 函数创建一个键(key),用以将不同函数使用的线程特有数据项区分开来。调用函数
    pthread_key_create()可创建此“键”,且只需在首个调用该函数的线程中创建一次,函数
    pthread_once()的使用正是出于这一目的。键在创建时并未分配任何线程特有数据块。
  • 调用 pthread_key_create()还有另一个目的,即允许调用者指定一个自定义解构函数,用于
    释放为该键所分配的各个存储块(参见下一步)。当使用线程特有数据的线程终止时,
    Pthreads API 会自动调用此解构函数,同时将该线程的数据块指针作为参数传入。
  • 函数会为每个调用者线程创建线程特有数据块。这一分配通过调用 malloc()(或类似函数)
    完成,每个线程只分配一次,且只会在线程初次调用此函数时分配。
  • 为了保存上一步所分配存储块的地址,函数会使用两个 Pthreads 函数:pthread_setspecific() 和 pthread_getspecific()。调用函数 pthread_setspecific() 实际上是对 Pthreads 实现发起这样的请求:保存该指针,并记录其与特定键(该函数的键)以及特定线程(调用者线程)的关联性。调用 pthread_getspecific()所执行的是互补操作:返回之前所保存的、与给定键以及调用线程相关联的指针。如果还没有指针与特定的键及线程相关联,那么 pthread_getspecific()返回 NULL。函数可以利用这一点来判断自身是否是初次为某个线程所调用,若为初次,则必须为该线程分配空间。
  • pthread_key_t : 线程本地变量 进程中相同的key变量 在不同的线程对应不同的地址空间.
    因为进程中的所有线程都可使用返回的键,所以参数 key 应指向一个全局变量。

    只要线程终止时与 key 的关联值不为 NULL,Pthreads API 会自动执行解构函数,并将与
    key 的关联值作为参数传入解构函数。传入的值通常是与该键关联,且指向线程特有数据块的
    指针。如果无需解构,那么可将 destructor 设置为 NULL。

    如果一个线程有多个线程特有数据块,那么对各个解构函数的调用顺序是不确定的。对
    每个解构函数的设计应相互独立。

    /*
    两个函数的返回值 :
        若成功,返回 0
        否则 返回错误编号
    */
    #include 
    
    int pthread_key_create(pthread_key_t * keyp, void (* destructor)(void *));
    /*
        (1) 创建线程特定数据 key,创建新建时,每个线程的数据地址设置为空值.
        (2) 线程正常退出(pthread_exit或者线程执行返回)时,destructor析构函数会被调用
        (3) 线程取消时,只有在最后的清理处理程序返回之后,destructor析构函数才会被调用
        (4) 如果线程调用了 exit ,_exit,_Exit 或者 abort ,或者出现其他非正常退出时,就不会调用析构函数
    */
    
    int pthread_key_delete(pthread_key_t key);
    /*
        取消与线程特定数据值之间的关联关系,调用pthread_key_delete并不会激活与键关联的析构函数
    */
    
    #include 
    /*
    返回值:
        若成功,返回线程特定数据值
        若没有值与该键关联 返回NULL
    */
    void* pthread_getspecific(pthread_key_t key);
    /*
    返回值:
        若成功,返回 0
        否则 返回错误编号
    */
    int pthread_setspecific(pthread_key_t key , const void * value); //为 key 绑定 value值
    /*
    参数 value 也可以不是一个指向内存区域的指针,而是任何可以赋值(通过强制转换)
    给 void*的标量值。在这种情况下,先前对 pthread_key_create()函数的调用应将 destructor
    指定为 NULL
    */
    

    线程信号

  • 每个线程都有自己的信号屏蔽字,但是信号的处理程序是进程中所有线程共享的.
  • 这意味着单个线程可以阻止某系信号,但当某个线程修改了与某个给定信号相关的处理行为以后,所有线程都必须共享这个处理行为
  • 进程中的信号是递送到单个线程的,如果一个信号与硬件故障相关,那么该信号一般会被发送到引起该事件的线程中去,而其他的信号则被发送到任意一个线程.
  • 操作线程信号掩码 pthread_sigmask()

    刚创建的新线程会从其创建者处继承信号掩码的一份拷贝。线程可以使用 pthread_sigmask()来改变或/并获取当前的信号掩码。

    #include 
    
    #define SIG_BLOCK     0        /* Block signals.  */
    #define SIG_UNBLOCK   1        /* Unblock signals.  */
    #define SIG_SETMASK   2        /* Set the set of blocked signals.  */
    
    /*
    两个个函数的返回值 :
        若成功,返回 0
        否则 返回错误编号
    */
    int pthread_sigmask (int how,const sigset_t * newmask, sigset_t * oldmask);
    
    /*
     how : 
         SIG_BLOCK : 将 set 指向信号集内的指定信号添加到信号掩码中
         SIG_UNBLOCK : 将 set 指向信号集中的信号从信号掩码中移除
         SIG_SETMASK : 将 set 指向的信号集赋给信号掩码
     newmask : 设置新的信号掩码。
     oldmask : 用于返回之前的信号掩码。
    */
    
    

    除了所操作的是线程信号掩码之外,pthread_sigmask()与 sigprocmask()的用法完全相同

    向线程发送信号 pthread_kill()

    函数 pthread_kill()向同一进程下的另一线程发送信号 sig。目标线程由参数 thread 标识

    #include 
    
    /*
    两个个函数的返回值 :
        若成功,返回 0
        否则 返回错误编号
    */
    int pthread_kill (pthread_t threadid, int signo);
    /*
        threadid : 线程 id 
        signo : 线程信号
    */
    
    

    因为仅在同一进程中可保证线程 ID 的唯一性,所以无法调用 pthread_ kill() 向其他进程中的线程发送信号。

    向同一进程中的另一线程发送携带数据的信号

    与函数 pthread_kill()一样,signo 表示将要发送的信号,thread 标识目标线程。参数 value 则指
    定了伴随信号的数据,其使用方式与函数 sigqueue()中的对应参数相同

    
     #include 
    
    /*
    返回值 :
        若成功,返回 0
        否则 返回错误编号
    */
    int pthread_sigqueue (pthread_t threadid, int signo,const union sigval value) ;
    
    

    以同步方式等待信号 sigwait()

    当多线程应用程序必须处理异步产生的信号时,通常不应该将信号处理函数作为接收信号到达的通知机制

    • 所有线程都阻塞进程可能接收的所有异步信号。最简单的方法是,在创建任何其他线程之
      前,由主线程阻塞这些信号。后续创建的每个线程都会继承主线程信号掩码的一份拷贝。

    • 再创建一个专用线程,调用函数 sigwaitinfo()、sigtimedwait()或 sigwait()来接收收到的
      信号

    函数 sigwait()会等待 set 所指信号集合中任一信号的到达,接收该信号,且在参数 sig 中
    将其返回。

    #include 
    
    /*
    返回值 :
        若成功,返回 0
        否则 返回错误编号
    */
    int sigwait (const sigset_t * set, int * sig) ;
    
    /*
    set : 感兴趣的信号集
    sig : 发生信号值的
    
    */
    

    多个线程在调用 sigwait()等待同一信号,那么当信号到达时只有一个线程会实际接收
    到,也无法确定收到信号的会是哪条线程。

    相关文章

    服务器端口转发,带你了解服务器端口转发
    服务器开放端口,服务器开放端口的步骤
    产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
    如何使用 WinGet 下载 Microsoft Store 应用
    百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
    百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

    发布评论