多线程编程指南

第 4 章 用同步对象编程

本章介绍了可用于线程的同步类型,还说明了使用同步的时间和方法。

同步对象是内存中的变量,可以按照与访问数据完全相同的方式对其进行访问。不同进程中的线程可以通过放在由线程控制的共享内存中的同步对象互相通信。尽管不同进程中的线程通常互不可见,但这些线程仍可以互相通信。

同步对象还可以放在文件中。同步对象可以比创建它的进程具有更长的生命周期。

同步对象具有以下可用类型:

同步的作用包括以下方面:


注 –

在 32 位体系结构上,long long 不是原子类型。(原子操作不能划分成更小的操作。)long long 可作为两个 32 位值进行读写。类型 intcharfloat 和指针在 SPARC Platform Edition 计算机和 Intel 体系结构的计算机上是原子类型。


互斥锁属性

使用互斥锁(互斥)可以使线程按顺序执行。通常,互斥锁通过确保一次只有一个线程执行代码的临界段来同步多个线程。互斥锁还可以保护单线程代码。

要更改缺省的互斥锁属性,可以对属性对象进行声明和初始化。通常,互斥锁属性会设置在应用程序开头的某个位置,以便可以快速查找和轻松修改。表 4–1 列出了用来处理互斥锁属性的函数。

表 4–1 互斥锁属性例程

操作

相关函数说明

初始化互斥锁属性对象 

pthread_mutexattr_init 语法

销毁互斥锁属性对象 

pthread_mutexattr_destroy 语法

设置互斥锁范围 

pthread_mutexattr_setpshared 语法

获取互斥锁范围 

pthread_mutexattr_getpshared 语法

设置互斥锁的类型属性 

pthread_mutexattr_settype 语法

获取互斥锁的类型属性 

pthread_mutexattr_gettype 语法

设置互斥锁属性的协议 

pthread_mutexattr_setprotocol 语法

获取互斥锁属性的协议 

pthread_mutexattr_getprotocol 语法

设置互斥锁属性的优先级上限 

pthread_mutexattr_setprioceiling 语法

获取互斥锁属性的优先级上限 

pthread_mutexattr_getprioceiling 语法

设置互斥锁的优先级上限 

pthread_mutex_setprioceiling 语法

获取互斥锁的优先级上限 

pthread_mutex_getprioceiling 语法

设置互斥锁的强健属性 

pthread_mutexattr_setrobust_np 语法

获取互斥锁的强健属性 

pthread_mutexattr_getrobust_np 语法

表 4–2 中显示了在定义互斥范围时 Solaris 线程和 POSIX 线程之间的差异。

表 4–2 互斥锁范围比较

Solaris

POSIX

定义

USYNC_PROCESS

PTHREAD_PROCESS_SHARED

用于同步该进程和其他进程中的线程 

USYNC_PROCESS_ROBUST

无 POSIX 等效项 

用于在进程间可靠地同步线程

USYNC_THREAD

PTHREAD_PROCESS_PRIVATE

用于仅同步该进程中的线程 

初始化互斥锁属性对象

使用 pthread_mutexattr_init(3C) 可以将与互斥锁对象相关联的属性初始化为其缺省值。在执行过程中,线程系统会为每个属性对象分配存储空间。

pthread_mutexattr_init 语法

int	pthread_mutexattr_init(pthread_mutexattr_t *mattr);
#include <pthread.h>



pthread_mutexattr_t mattr;

int ret;



/* initialize an attribute to default value */

ret = pthread_mutexattr_init(&mattr); 

调用此函数时,pshared 属性的缺省值为 PTHREAD_PROCESS_PRIVATE。该值表示可以在进程内使用经过初始化的互斥锁。

mattr 的类型为 opaque,其中包含一个由系统分配的属性对象。mattr 范围可能的值为 PTHREAD_PROCESS_PRIVATEPTHREAD_PROCESS_SHAREDPTHREAD_PROCESS_PRIVATE 是缺省值。

对于互斥锁属性对象,必须首先通过调用 pthread_mutexattr_destroy(3C) 将其销毁,才能重新初始化该对象。pthread_mutexattr_init() 调用会导致分配类型为 opaque 的对象。如果未销毁该对象,则会导致内存泄漏。

pthread_mutexattr_init 返回值

pthread_mutexattr_init() 成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。


ENOMEM

描述:

内存不足,无法初始化互斥锁属性对象。

销毁互斥锁属性对象

pthread_mutexattr_destroy(3C) 可用来取消分配用于维护 pthread_mutexattr_init() 所创建的属性对象的存储空间。

pthread_mutexattr_destroy 语法

int	pthread_mutexattr_destroy(pthread_mutexattr_t *mattr)
#include <pthread.h>

pthread_mutexattr_t mattr;

int ret;



/* destroy an attribute */

ret = pthread_mutexattr_destroy(&mattr); 

pthread_mutexattr_destroy 返回值

pthread_mutexattr_destroy() 成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。


EINVAL

描述:

mattr 指定的值无效。

设置互斥锁的范围

pthread_mutexattr_setpshared(3C) 可用来设置互斥锁变量的作用域。

pthread_mutexattr_setpshared 语法

int	pthread_mutexattr_setpshared(pthread_mutexattr_t *mattr,

    int pshared);
#include <pthread.h>



pthread_mutexattr_t mattr;

int ret;



ret = pthread_mutexattr_init(&mattr);

/*

 * resetting to its default value: private

 */

ret = pthread_mutexattr_setpshared(&mattr,

     PTHREAD_PROCESS_PRIVATE);

互斥锁变量可以是进程专用的(进程内)变量,也可以是系统范围内的(进程间)变量。要在多个进程中的线程之间共享互斥锁,可以在共享内存中创建互斥锁,并将 pshared 属性设置为 PTHREAD_PROCESS_SHARED。 此行为与最初的 Solaris 线程实现中 mutex_init() 中的 USYNC_PROCESS 标志等效。

如果互斥锁的 pshared 属性设置为 PTHREAD_PROCESS_PRIVATE,则仅有那些由同一个进程创建的线程才能够处理该互斥锁。

pthread_mutexattr_setpshared 返回值

pthread_mutexattr_setpshared() 成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。


EINVAL

描述:

mattr 指定的值无效。

获取互斥锁的范围

pthread_mutexattr_getpshared(3C) 可用来返回由 pthread_mutexattr_setpshared() 定义的互斥锁变量的范围。

pthread_mutexattr_getpshared 语法

int	pthread_mutexattr_getpshared(pthread_mutexattr_t *mattr,

    int *pshared);
#include <pthread.h>



pthread_mutexattr_t mattr;

int pshared, ret;



/* get pshared of mutex */

ret = pthread_mutexattr_getpshared(&mattr, &pshared); 

此函数可为属性对象 mattr 获取 pshared 的当前值。该值为 PTHREAD_PROCESS_SHAREDPTHREAD_PROCESS_PRIVATE

pthread_mutexattr_getpshared 返回值

pthread_mutexattr_getpshared() 成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。


EINVAL

描述:

mattr 指定的值无效。

设置互斥锁类型的属性

pthread_mutexattr_settype(3C) 可用来设置互斥锁的 type 属性。

pthread_mutexattr_settype 语法

#include <pthread.h>



int pthread_mutexattr_settype(pthread_mutexattr_t  *attr , int type);

类型属性的缺省值为 PTHREAD_MUTEX_DEFAULT

type 参数指定互斥锁的类型。以下列出了有效的互斥锁类型:


PTHREAD_MUTEX_NORMAL

描述:

此类型的互斥锁不会检测死锁。如果线程在不首先解除互斥锁的情况下尝试重新锁定该互斥锁,则会产生死锁。尝试解除由其他线程锁定的互斥锁会产生不确定的行为。如果尝试解除锁定的互斥锁未锁定,则会产生不确定的行为。


PTHREAD_MUTEX_ERRORCHECK

描述:

此类型的互斥锁可提供错误检查。如果线程在不首先解除锁定互斥锁的情况下尝试重新锁定该互斥锁,则会返回错误。如果线程尝试解除锁定的互斥锁已经由其他线程锁定,则会返回错误。如果线程尝试解除锁定的互斥锁未锁定,则会返回错误。


PTHREAD_MUTEX_RECURSIVE

描述:

如果线程在不首先解除锁定互斥锁的情况下尝试重新锁定该互斥锁,则可成功锁定该互斥锁。 与 PTHREAD_MUTEX_NORMAL 类型的互斥锁不同,对此类型互斥锁进行重新锁定时不会产生死锁情况。多次锁定互斥锁需要进行相同次数的解除锁定才可以释放该锁,然后其他线程才能获取该互斥锁。如果线程尝试解除锁定的互斥锁已经由其他线程锁定,则会返回错误。 如果线程尝试解除锁定的互斥锁未锁定,则会返回错误。


PTHREAD_MUTEX_DEFAULT

描述:

如果尝试以递归方式锁定此类型的互斥锁,则会产生不确定的行为。对于不是由调用线程锁定的此类型互斥锁,如果尝试对它解除锁定,则会产生不确定的行为。对于尚未锁定的此类型互斥锁,如果尝试对它解除锁定,也会产生不确定的行为。允许在实现中将该互斥锁映射到其他互斥锁类型之一。对于 Solaris 线程,PTHREAD_PROCESS_DEFAULT 会映射到 PTHREAD_PROCESS_NORMAL

pthread_mutexattr_settype 返回值

如果运行成功,pthread_mutexattr_settype 函数会返回零。否则,将返回用于指明错误的错误号。


EINVAL

描述:

值为 type 无效。


EINVAL

描述:

attr 指定的值无效。

获取互斥锁的类型属性

pthread_mutexattr_gettype(3C) 可用来获取由 pthread_mutexattr_settype() 设置的互斥锁的 type 属性。

pthread_mutexattr_gettype 语法

#include <pthread.h>



int pthread_mutexattr_gettype(pthread_mutexattr_t  *attr , int  *type);

类型属性的缺省值为 PTHREAD_MUTEX_DEFAULT

type 参数指定互斥锁的类型。有效的互斥锁类型包括:

有关每种类型的说明,请参见pthread_mutexattr_settype 语法

pthread_mutexattr_gettype 返回值

如果成功完成,pthread_mutexattr_gettype() 会返回 0。其他任何返回值都表示出现了错误。

设置互斥锁属性的协议

pthread_mutexattr_setprotocol(3C) 可用来设置互斥锁属性对象的协议属性。

pthread_mutexattr_setprotocol 语法

#include <pthread.h>



int pthread_mutexattr_setprotocol(pthread_mutexattr_t *attr, int protocol);

attr 指示以前调用 pthread_mutexattr_init() 时创建的互斥锁属性对象。

protocol 可定义应用于互斥锁属性对象的协议。

pthread.h 中定义的 protocol 可以是以下值之一:PTHREAD_PRIO_NONEPTHREAD_PRIO_INHERITPTHREAD_PRIO_PROTECT

如果某个线程调用 sched_setparam() 来更改初始优先级,则调度程序不会采用新优先级将该线程移到调度队列末尾。

一个线程可以同时拥有多个混合使用 PTHREAD_PRIO_INHERITPTHREAD_PRIO_PROTECT 初始化的互斥锁。在这种情况下,该线程将以通过其中任一协议获取的最高优先级执行。

pthread_mutexattr_setprotocol 返回值

如果成功完成,pthread_mutexattr_setprotocol() 会返回 0。其他任何返回值都表示出现了错误。

如果出现以下任一情况,pthread_mutexattr_setprotocol() 将失败并返回对应的值。


ENOSYS

描述:

选项 _POSIX_THREAD_PRIO_INHERIT_POSIX_THREAD_PRIO_PROTECT 均未定义并且该实现不支持此函数。


ENOTSUP

描述:

protocol 指定的值不受支持。

如果出现以下任一情况,pthread_mutexattr_setprotocol() 可能会失败并返回对应的值。


EINVAL

描述:

attrprotocol 指定的值无效。


EPERM

描述:

调用方无权执行该操作。

获取互斥锁属性的协议

pthread_mutexattr_getprotocol(3C) 可用来获取互斥锁属性对象的协议属性。

pthread_mutexattr_getprotocol 语法

#include <pthread.h>



int pthread_mutexattr_getprotocol(const pthread_mutexattr_t *attr, 

											int *protocol);

attr 指示以前调用 pthread_mutexattr_init() 时创建的互斥锁属性对象。

protocol 包含以下协议属性之一:PTHREAD_PRIO_NONEPTHREAD_PRIO_INHERITPTHREAD_PRIO_PROTECT

pthread_mutexattr_getprotocol 返回值

如果成功完成,pthread_mutexattr_getprotocol() 会返回 0。其他任何返回值都表示出现了错误。

如果出现以下情况,pthread_mutexattr_getprotocol() 将失败并返回对应的值。


ENOSYS

描述:

_POSIX_THREAD_PRIO_INHERIT 选项和 _POSIX_THREAD_PRIO_PROTECT 选项均未定义并且该实现不支持此函数。

如果出现以下任一情况,pthread_mutexattr_getprotocol() 可能会失败并返回对应的值。


EINVAL

描述:

attr 指定的值无效。


EPERM

描述:

调用方无权执行该操作。

设置互斥锁属性的优先级上限

pthread_mutexattr_setprioceiling(3C) 可用来设置互斥锁属性对象的优先级上限属性。

pthread_mutexattr_setprioceiling 语法

#include <pthread.h>



int pthread_mutexattr_setprioceiling(pthread_mutexatt_t *attr, 

				int prioceiling, int *oldceiling);

attr 指示以前调用 pthread_mutexattr_init() 时创建的互斥锁属性对象。

prioceiling 指定已初始化互斥锁的优先级上限。优先级上限定义执行互斥锁保护的临界段时的最低优先级。prioceiling 位于 SCHED_FIFO 所定义的优先级的最大范围内。要避免优先级倒置,请将 prioceiling 设置为高于或等于可能会锁定特定互斥锁的所有线程的最高优先级。

oldceiling 包含以前的优先级上限值。

pthread_mutexattr_setprioceiling 返回值

如果成功完成,pthread_mutexattr_setprioceiling() 会返回 0。其他任何返回值都表示出现了错误。

如果出现以下任一情况,pthread_mutexattr_setprioceiling() 将失败并返回对应的值。


ENOSYS

描述:

选项 _POSIX_THREAD_PRIO_PROTECT 未定义并且该实现不支持此函数。

如果出现以下任一情况,pthread_mutexattr_setprioceiling() 可能会失败并返回对应的值。


EINVAL

描述:

attrprioceiling 指定的值无效。


EPERM

描述:

调用方无权执行该操作。

获取互斥锁属性的优先级上限

pthread_mutexattr_getprioceiling(3C) 可用来获取互斥锁属性对象的优先级上限属性。

pthread_mutexattr_getprioceiling 语法

#include <pthread.h>



int pthread_mutexattr_getprioceiling(const pthread_mutexatt_t *attr, 

						int *prioceiling);

attr 指定以前调用 pthread_mutexattr_init() 时创建的属性对象。


注 –

仅当定义了 _POSIX_THREAD_PRIO_PROTECT 符号时,attr 互斥锁属性对象才会包括优先级上限属性。


pthread_mutexattr_getprioceiling() 返回 prioceiling 中已初始化互斥锁的优先级上限。优先级上限定义执行互斥锁保护的临界段时的最低优先级。prioceiling 位于 SCHED_FIFO 所定义的优先级的最大范围内。要避免优先级倒置,请将 prioceiling 设置为高于或等于可能会锁定特定互斥锁的所有线程的最高优先级。

pthread_mutexattr_getprioceiling 返回值

如果成功完成,pthread_mutexattr_getprioceiling() 会返回 0。其他任何返回值都表示出现了错误。

如果出现以下任一情况,pthread_mutexattr_getprioceiling() 将失败并返回对应的值。


ENOSYS

描述:

_POSIX_THREAD_PRIO_PROTECT 选项未定义并且该实现不支持此函数。

如果出现以下任一情况,pthread_mutexattr_getprioceiling() 可能会失败并返回对应的值。


EINVAL

描述:

attr 指定的值无效。


EPERM

描述:

调用方无权执行该操作。

设置互斥锁的优先级上限

pthread_mutexattr_setprioceiling(3C) 可用来设置互斥锁的优先级上限。

pthread_mutex_setprioceiling 语法

#include <pthread.h>



int pthread_mutex_setprioceiling(pthread_mutex_t *mutex, 

					int prioceiling, int *old_ceiling);

pthread_mutex_setprioceiling() 可更改互斥锁 mutex 的优先级上限 prioceilingpthread_mutex_setprioceiling() 可锁定互斥锁(如果未锁定的话),或者一直处于阻塞状态,直到 pthread_mutex_setprioceiling() 成功锁定该互斥锁,更改该互斥锁的优先级上限并将该互斥锁释放为止。锁定互斥锁的过程无需遵循优先级保护协议。

如果 pthread_mutex_setprioceiling() 成功,则将在 old_ceiling 中返回以前的优先级上限值。如果 pthread_mutex_setprioceiling() 失败,则互斥锁的优先级上限保持不变。

pthread_mutex_setprioceiling 返回值

如果成功完成,pthread_mutex_setprioceiling() 会返回 0。其他任何返回值都表示出现了错误。

如果出现以下情况,pthread_mutexatt_setprioceiling() 将失败并返回对应的值。


ENOSYS

描述:

选项_POSIX_THREAD_PRIO_PROTECT 未定义并且该实现不支持此函数。

如果出现以下任一情况,pthread_mutex_setprioceiling() 可能会失败并返回对应的值。


EINVAL

描述:

prioceiling 所请求的优先级超出了范围。


EINVAL

描述:

mutex 指定的值不会引用当前存在的互斥锁。


ENOSYS

描述:

该实现不支持互斥锁的优先级上限协议。


EPERM

描述:

调用方无权执行该操作。

获取互斥锁的优先级上限

pthread_mutexattr_getprioceiling(3C) 可用来获取互斥锁的优先级上限。

pthread_mutex_getprioceiling 语法

#include <pthread.h>



int pthread_mutex_getprioceiling(const pthread_mutex_t *mutex, 

                    int *prioceiling);

pthread_mutex_getprioceiling() 会返回 mutex 的优先级上限 prioceiling

pthread_mutex_getprioceiling 返回值

如果成功完成,pthread_mutex_getprioceiling() 会返回 0。其他任何返回值都表示出现了错误。

如果出现以下任一情况,pthread_mutexatt_getprioceiling() 将失败并返回对应的值。


ENOSYS

描述:

_POSIX_THREAD_PRIO_PROTECT 选项未定义并且该实现不支持此函数。

如果出现以下任一情况,pthread_mutex_getprioceiling() 可能会失败并返回对应的值。


EINVAL

描述:

mutex 指定的值不会引用当前存在的互斥锁。


ENOSYS

描述:

该实现不支持互斥锁的优先级上限协议。


EPERM

描述:

调用方无权执行该操作。

设置互斥锁的强健属性

pthread_mutexattr_setrobust_np(3C) 可用来设置互斥锁属性对象的强健属性。

pthread_mutexattr_setrobust_np 语法

#include <pthread.h>



int pthread_mutexattr_setrobust_np(pthread_mutexattr_t *attr, 

												int *robustness);

注 –

仅当定义了符号 _POSIX_THREAD_PRIO_INHERIT 时,pthread_mutexattr_setrobust_np() 才适用。


attr 指示以前通过调用 pthread_mutexattr_init() 创建的互斥锁属性对象。

robustness 定义在互斥锁的属主失败时的行为。pthread.h 中定义的 robustness 的值为 PTHREAD_MUTEX_ROBUST_NPPTHREAD_MUTEX_STALLED_NP。缺省值为 PTHREAD_MUTEX_STALLED_NP

pthread_mutexattr_setrobust_np 返回值

如果成功完成,pthread_mutexattr_setrobust_np() 会返回 0。其他任何返回值都表示出现了错误。

如果出现以下任一情况,pthread_mutexattr_setrobust_np() 将失败并返回对应的值。


ENOSYS

描述:

选项 _POSIX_THREAD_PRIO__INHERIT 未定义,或者该实现不支持 pthread_mutexattr_setrobust_np()


ENOTSUP

描述:

robustness 指定的值不受支持。

pthread_mutexattr_setrobust_np() 可能会在出现以下情况时失败:


EINVAL

描述:

attrrobustness 指定的值无效。

获取互斥锁的强健属性

pthread_mutexattr_getrobust_np(3C) 可用来获取互斥锁属性对象的强健属性。

pthread_mutexattr_getrobust_np 语法

#include <pthread.h>



int pthread_mutexattr_getrobust_np(const pthread_mutexattr_t *attr, 

												int *robustness);

注 –

仅当定义了符号 _POSIX_THREAD_PRIO_INHERIT 时,pthread_mutexattr_getrobust_np() 才适用。


attr 指示以前通过调用 pthread_mutexattr_init() 创建的互斥锁属性对象。

robustness 是互斥锁属性对象的强健属性值。

pthread_mutexattr_getrobust_np 返回值

如果成功完成,pthread_mutexattr_getrobust_np() 会返回 0。其他任何返回值都表示出现了错误。

如果出现以下任一情况,pthread_mutexattr_getrobust_np() 将失败并返回对应的值。


ENOSYS

描述:

选项 _POSIX_THREAD_PRIO__INHERIT 未定义,或者该实现不支持 pthread_mutexattr_getrobust_np()

pthread_mutexattr_getrobust_np() 可能会在出现以下情况时失败:


EINVAL

描述:

attrrobustness 指定的值无效。

使用互斥锁

表 4–3 列出了用来处理互斥锁的函数。

表 4–3 互斥锁的例程

操作

相关函数说明

初始化互斥锁 

pthread_mutex_init 语法

使互斥锁保持一致 

pthread_mutex_consistent_np 语法

锁定互斥锁 

pthread_mutex_lock 语法

解除锁定互斥锁 

pthread_mutex_unlock 语法

使用非阻塞互斥锁锁定 

pthread_mutex_trylock 语法

销毁互斥锁 

pthread_mutex_destroy 语法

缺省调度策略 SCHED_OTHER 不指定线程可以获取锁的顺序。如果多个线程正在等待一个互斥锁,则获取顺序是不确定的。出现争用时,缺省行为是按优先级顺序解除线程的阻塞。

初始化互斥锁

使用 pthread_mutex_init(3C) 可以使用缺省值初始化由 mp 所指向的互斥锁,还可以指定已经使用 pthread_mutexattr_init() 设置的互斥锁属性。mattr 的缺省值为 NULL。对于 Solaris 线程,请参见mutex_init(3C) 语法

pthread_mutex_init 语法

int	pthread_mutex_init(pthread_mutex_t *mp,

    const pthread_mutexattr_t *mattr);
#include <pthread.h>



pthread_mutex_t mp = PTHREAD_MUTEX_INITIALIZER;

pthread_mutexattr_t mattr;

int ret;



/* initialize a mutex to its default value */

ret = pthread_mutex_init(&mp, NULL);



/* initialize a mutex */

ret = pthread_mutex_init(&mp, &mattr); 

如果互斥锁已初始化,则它会处于未锁定状态。互斥锁可以位于进程之间共享的内存中或者某个进程的专用内存中。


注 –

初始化互斥锁之前,必须将其所在的内存清零。


mattr 设置为 NULL 的效果与传递缺省互斥锁属性对象的地址相同,但是没有内存开销。

使用 PTHREAD_MUTEX_INITIALIZER 宏可以将以静态方式定义的互斥锁初始化为其缺省属性。

当其他线程正在使用某个互斥锁时,请勿重新初始化或销毁该互斥锁。如果任一操作没有正确完成,将会导致程序失败。如果要重新初始化或销毁某个互斥锁,则应用程序必须确保当前未使用该互斥锁。

pthread_mutex_init 返回值

pthread_mutex_init() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。


EBUSY

描述:

该实现已检测到系统尝试重新初始化 mp 所引用的对象,即以前进行过初始化但 尚未销毁的互斥锁。


EINVAL

描述:

mattr 属性值无效。互斥锁尚未修改。


EFAULT

描述:

mp 所指向的互斥锁的地址无效。

使互斥保持一致

如果某个互斥锁的属主失败,该互斥锁可能会变为不一致。

使用 pthread_mutex_consistent_np 可使互斥对象 mutex 在其属主停止之后保持一致。

pthread_mutex_consistent_np 语法

#include <pthread.h>

int	pthread_mutex_consistent_np(pthread_mutex_t *mutex); 

注 –

仅当定义了 _POSIX_THREAD_PRIO_INHERIT 符号时,pthread_mutex_consistent_np() 才适用,并且仅适用于使用协议属性值 PTHREAD_PRIO_INHERIT 初始化的互斥锁。


调用 pthread_mutex_lock() 会获取不一致的互斥锁。EOWNWERDEAD 返回值表示出现不一致的互斥锁。

持有以前通过调用 pthread_mutex_lock() 获取的互斥锁时可调用 pthread_mutex_consistent_np()

如果互斥锁的属主失败,则该互斥锁保护的临界段可能会处于不一致状态。在这种情况下,仅当互斥锁保护的临界段可保持一致时,才能使该互斥锁保持一致。

针对互斥锁调用 pthread_mutex_lock()pthread_mutex_unlock()pthread_mutex_trylock() 会以正常方式进行。

对于一致或者未持有的互斥锁,pthread_mutex_consistent_np() 的行为是不确定的。

pthread_mutex_consistent_np 返回值

pthread_mutex_consistent_np() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。

pthread_mutex_consistent_np() 会在出现以下情况时失败:


ENOSYS

描述:

选项 _POSIX_THREAD_PRIO_INHERIT 未定义,或者该实现不支持 pthread_mutex_consistent_np()

pthread_mutex_consistent_np() 可能会在出现以下情况时失败:


EINVAL

描述:

mattr 属性值无效。

锁定互斥锁

使用 pthread_mutex_lock(3C) 可以锁定 mutex 所指向的互斥锁。

pthread_mutex_lock 语法

int	pthread_mutex_lock(pthread_mutex_t *mutex); 
#include <pthread.h>



pthread_mutex_t mutex;

int ret;



ret = pthread_ mutex_lock(&mp); /* acquire the mutex */

pthread_mutex_lock() 返回时,该互斥锁已被锁定。调用线程是该互斥锁的属主。如果该互斥锁已被另一个线程锁定和拥有,则调用线程将阻塞,直到该互斥锁变为可用为止。 对于 Solaris 线程,请参见mutex_lock 语法

如果互斥锁类型为 PTHREAD_MUTEX_NORMAL,则不提供死锁检测。尝试重新锁定互斥锁会导致死锁。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或未锁定,则将产生不确定的行为。

如果互斥锁类型为 PTHREAD_MUTEX_ERRORCHECK,则会提供错误检查。如果某个线程尝试重新锁定的互斥锁已经由该线程锁定,则将返回错误。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或者未锁定,则将返回错误。

如果互斥锁类型为 PTHREAD_MUTEX_RECURSIVE,则该互斥锁会保留锁定计数这一概念。线程首次成功获取互斥锁时,锁定计数会设置为 1。线程每重新锁定该互斥锁一次,锁定计数就增加 1。线程每解除锁定该互斥锁一次,锁定计数就减小 1。 锁定计数达到 0 时,该互斥锁即可供其他线程获取。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或者未锁定,则将返回错误。

如果互斥锁类型是 PTHREAD_MUTEX_DEFAULT,则尝试以递归方式锁定该互斥锁将产生不确定的行为。对于不是由调用线程锁定的互斥锁,如果尝试解除对它的锁定,则会产生不确定的行为。如果尝试解除锁定尚未锁定的互斥锁,则会产生不确定的行为。

pthread_mutex_lock 返回值

pthread_mutex_lock() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。


EAGAIN

描述:

由于已超出了互斥锁递归锁定的最大次数,因此无法获取该互斥锁。


EDEADLK

描述:

当前线程已经拥有互斥锁。

如果定义了 _POSIX_THREAD_PRIO_INHERIT 符号,则会使用协议属性值 PTHREAD_PRIO_INHERIT 对互斥锁进行初始化。此外,如果 pthread_mutexattr_setrobust_np()robustness 参数是 PTHREAD_MUTEX_ROBUST_NP,则该函数将失败并返回以下值之一:


EOWNERDEAD

描述:

该互斥锁的最后一个属主在持有该互斥锁时失败。该互斥锁现在由调用方拥有。调用方必须尝试使该互斥锁所保护的状态一致。

如果调用方能够使状态保持一致,请针对该互斥锁调用 pthread_mutex_consistent_np() 并解除锁定该互斥锁。以后对 pthread_mutex_lock() 的调用都将正常进行。

如果调用方无法使状态保持一致,请勿针对该互斥锁调用 pthread_mutex_init(),但要解除锁定该互斥锁。以后调用 pthread_mutex_lock() 时将无法获取该互斥锁,并且将返回错误代码 ENOTRECOVERABLE

如果获取该锁的属主失败并返回 EOWNERDEAD,则下一个属主获取该锁时将返回 EOWNERDEAD


ENOTRECOVERABLE

描述:

尝试获取的互斥锁正在保护某个状态,此状态由于该互斥锁以前的属主在持有该锁时失败而导致不可恢复。尚未获取该互斥锁。如果满足以下条件,则可能出现此不可恢复的情况:

  • 以前获取该锁时返回 EOWNERDEAD

  • 该属主无法清除此状态

  • 该属主已经解除锁定了该互斥锁,但是没有使互斥锁状态保持一致


ENOMEM

描述:

已经超出了可同时持有的互斥锁数目的限制。

解除锁定互斥锁

使用 pthread_mutex_unlock(3C) 可以解除锁定 mutex 所指向的互斥锁。 对于 Solaris 线程,请参见mutex_unlock 语法

pthread_mutex_unlock 语法

int	pthread_mutex_unlock(pthread_mutex_t *mutex); 
#include <pthread.h>



pthread_mutex_t mutex;

int ret;



ret = pthread_mutex_unlock(&mutex); /* release the mutex */

pthread_mutex_unlock() 可释放 mutex 引用的互斥锁对象。互斥锁的释放方式取决于互斥锁的类型属性。 如果调用 pthread_mutex_unlock() 时有多个线程被 mutex 对象阻塞,则互斥锁变为可用时调度策略可确定获取该互斥锁的线程。 对于 PTHREAD_MUTEX_RECURSIVE 类型的互斥锁,当计数达到零并且调用线程不再对该互斥锁进行任何锁定时,该互斥锁将变为可用。

pthread_mutex_unlock 返回值

pthread_mutex_unlock() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。


EPERM

描述:

当前线程不拥有互斥锁。

使用非阻塞互斥锁锁定

使用 pthread_mutex_trylock(3C) 可以尝试锁定 mutex 所指向的互斥锁。对于 Solaris 线程,请参见mutex_trylock 语法

pthread_mutex_trylock 语法

int	pthread_mutex_trylock(pthread_mutex_t *mutex); 
#include <pthread.h>



pthread_mutex_t mutex;

int ret;



ret = pthread_mutex_trylock(&mutex); /* try to lock the mutex */

pthread_mutex_trylock()pthread_mutex_lock() 的非阻塞版本。如果 mutex 所引用的互斥对象当前被任何线程(包括当前线程)锁定,则将立即返回该调用。否则,该互斥锁将处于锁定状态,调用线程是其属主。

pthread_mutex_trylock 返回值

pthread_mutex_trylock() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。


EBUSY

描述:

由于 mutex 所指向的互斥锁已锁定,因此无法获取该互斥锁。


EAGAIN

描述:

由于已超出了 mutex 的递归锁定最大次数,因此无法获取该互斥锁。

如果定义了 _POSIX_THREAD_PRIO_INHERIT 符号,则会使用协议属性值 PTHREAD_PRIO_INHERIT 对互斥锁进行初始化。此外,如果 pthread_mutexattr_setrobust_np()robustness 参数是 PTHREAD_MUTEX_ROBUST_NP,则该函数将失败并返回以下值之一:


EOWNERDEAD

描述:

该互斥锁的最后一个属主在持有该互斥锁时失败。该互斥锁现在由调用方拥有。调用方必须尝试使该互斥锁所保护的状态一致。

如果调用方能够使状态保持一致,请针对该互斥锁调用 pthread_mutex_consistent_np() 并解除锁定该互斥锁。以后对 pthread_mutex_lock() 的调用都将正常进行。

如果调用方无法使状态保持一致,请勿针对该互斥锁调用 pthread_mutex_init(),而要解除锁定该互斥锁。以后调用 pthread_mutex_trylock() 时将无法获取该互斥锁,并且将返回错误代码 ENOTRECOVERABLE

如果已获取该锁的属主失败并返回 EOWNERDEAD,则下一个属主获取该锁时返回 EOWNERDEAD


ENOTRECOVERABLE

描述:

尝试获取的互斥锁正在保护某个状态,此状态由于该互斥锁以前的属主在持有该锁时失败而导致不可恢复。尚未获取该互斥锁。 以下条件下可能会出现此情况:

  • 以前获取该锁时返回 EOWNERDEAD

  • 该属主无法清除此状态

  • 该属主已经解除锁定了该互斥锁,但是没有使互斥锁状态保持一致


ENOMEM

描述:

已经超出了可同时持有的互斥锁数目的限制。

销毁互斥锁

使用 pthread_mutex_destroy(3C) 可以销毁与 mp 所指向的互斥锁相关联的任何状态。 对于 Solaris 线程,请参见mutex_destroy 语法

pthread_mutex_destroy 语法

int	pthread_mutex_destroy(pthread_mutex_t *mp); 
#include <pthread.h>



pthread_mutex_t mp;

int ret;



ret = pthread_mutex_destroy(&mp); /* mutex is destroyed */

请注意,没有释放用来存储互斥锁的空间。

pthread_mutex_destroy 返回值

pthread_mutex_destroy() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。


EINVAL

描述:

mp 指定的值不会引用已初始化的互斥锁对象。

互斥锁定的代码示例

示例 4–1 显示了使用互斥锁定的一些代码段。


示例 4–1 互斥锁示例

#include <pthread.h>



pthread_mutex_t count_mutex;

long long count;



void

increment_count()

{

	    pthread_mutex_lock(&count_mutex);

    count = count + 1;

	    pthread_mutex_unlock(&count_mutex);

}



long long

get_count()

{

    long long c;

    

    pthread_mutex_lock(&count_mutex);

	    c = count;

    pthread_mutex_unlock(&count_mutex);

	    return (c);

}

示例 4–1 中的两个函数将互斥锁用于不同目的。increment_count() 函数使用互斥锁确保对共享变量进行原子更新。get_count() 函数使用互斥锁保证以原子方式读取 64 位数量 count。在 32 位体系结构上,long long 实际上是两个 32 位数量。

读取整数值时执行的是原子运算,因为整数是大多数计算机中常见的字长。

锁分层结构的使用示例

有时,可能需要同时访问两个资源。您可能正在使用其中的一个资源,随后发现还需要另一个资源。如果两个线程尝试声明这两个资源,但是以不同的顺序锁定与这些资源相关联的互斥锁,则会出现问题。例如,如果两个线程分别锁定互斥锁 1 和互斥锁 2,则每个线程尝试锁定另一个互斥锁时,将会出现死锁。示例 4–2 说明了可能的死锁情况。


示例 4–2 死锁

线程 1

线程 2

 

pthread_mutex_lock(&m1);

 

 

 

 

/* use resource 1 */ 

 

pthread_mutex_lock(&m2);

 

 

/* use resources 1 and 2 */ 

pthread_mutex_unlock(&m2);

pthread_mutex_unlock(&m1);

pthread_mutex_lock(&m2);

 

 

/* use resource 2 */ 

 

pthread_mutex_lock(&m1);

 

/* use resources 1 and 2 */ 

pthread_mutex_unlock(&m1);

pthread_mutex_unlock(&m2);

 


避免此问题的最佳方法是,确保线程在锁定多个互斥锁时,以同样的顺序进行锁定。如果始终按照规定的顺序锁定,就不会出现死锁。此方法称为锁分层结构,它通过为互斥锁指定逻辑编号来对这些锁进行排序。

另外,请注意以下限制:如果您持有的任何互斥锁其指定编号大于 n,则不能提取指定编号为 n 的互斥锁。

但是,不能始终使用此方法。有时,必须按照与规定不同的顺序提取互斥锁。要防止在这种情况下出现死锁,请使用 pthread_mutex_trylock()。如果线程发现无法避免死锁时,该线程必须释放其互斥锁。


示例 4–3 条件锁定

线程 1

线程 2

pthread_mutex_lock(&m1); pthread_mutex_lock(&m2);

 

 

 

 

/* no processing */ 

 

pthread_mutex_unlock(&m2);

pthread_mutex_unlock(&m1);

for (; ;)

{ pthread_mutex_lock(&m2);

 

 

if(pthread_mutex_trylock(&m1)==0)

/* got it */  

break;

/* didn't get it */ 

pthread_mutex_unlock(&m2);

}

/* get locks; no processing */ 

pthread_mutex_unlock(&m1);

pthread_mutex_unlock(&m2);


示例 4–3 中,线程 1 按照规定的顺序锁定互斥锁,但是线程 2 不按顺序提取互斥锁。要确保不会出现死锁,线程 2 必须非常小心地提取互斥锁 1。如果线程 2 在等待该互斥锁释放时被阻塞,则线程 2 可能刚才已经与线程 1 进入了死锁状态。

要确保线程 2 不会进入死锁状态,线程 2 需要调用 pthread_mutex_trylock(),此函数可在该互斥锁可用时提取它。如果该互斥锁不可用,线程 2 将立即返回并报告提取失败。此时,线程 2 必须释放互斥锁 2。线程 1 现在会锁定互斥锁 2,然后释放互斥锁 1 和互斥锁 2。

嵌套锁定和单链接列表的结合使用示例

示例 4–4示例 4–5 说明了如何同时提取三个锁。通过按照规定的顺序提取锁可避免出现死锁。


示例 4–4 单链接列表结构

typedef struct node1 {

    int value;

    struct node1 *link;

    pthread_mutex_t lock;

} node1_t;



node1_t ListHead;

本示例针对每个包含一个互斥锁的节点使用单链接列表结构。要将某个节点从列表中删除,请首先从 ListHead 开始搜索列表,直到找到所需的节点为止。ListHead 永远不会被删除。

要防止执行此搜索时产生并发删除,请在访问每个节点的任何内容之前先锁定该节点。由于所有的搜索都从 ListHead 开始,并且始终按照列表中的顺序提取锁,因此不会出现死锁。

因为更改涉及到两个节点,所以找到所需的节点之后,请锁定该节点及其前序节点。因为前序节点的锁总是最先提取,所以可再次防止出现死锁。示例 4–5 说明如何使用 C 代码来删除单链接列表中的项。


示例 4–5 单链接列表和嵌套锁定

node1_t *delete(int value)

{

    node1_t *prev, *current;



    prev = &ListHead;

    pthread_mutex_lock(&prev->lock);

    while ((current = prev->link) != NULL) {

        pthread_mutex_lock(&current->lock);

        if (current->value == value) {

            prev->link = current->link;

            pthread_mutex_unlock(&current->lock);

            pthread_mutex_unlock(&prev->lock);

            current->link = NULL;

            return(current);

        }

        pthread_mutex_unlock(&prev->lock);

        prev = current;

    }

    pthread_mutex_unlock(&prev->lock);

    return(NULL);

}

嵌套锁定和循环链接列表的示例

示例 4–6 通过将以前的列表结构转换为循环列表来对其进行修改。由于不再存在用于标识的头节点,因该线程可以与特定的节点相关联,并可针对该节点及其邻居执行操作。锁分层结构在此处不适用,因为链接之后的分层结构明显是循环结构。


示例 4–6 循环链接列表结构

typedef struct node2 {

    int value;

    struct node2 *link;

    pthread_mutex_t lock;

} node2_t;

以下的 C 代码用来获取两个节点上的锁并执行涉及到这两个锁的操作。


示例 4–7 循环链接列表和嵌套锁定

void Hit Neighbor(node2_t *me) {

    while (1) {

        pthread_mutex_lock(&me->lock);

        if (pthread_mutex_lock(&me->link->lock)!= 0) {

            /* failed to get lock */             

            pthread_mutex_unlock(&me->lock);              

            continue;         

        }         

        break;     

    }     

    me->link->value += me->value;     

    me->value /=2;     

    pthread_mutex_unlock(&me->link->lock);     

    pthread_mutex_unlock(&me->lock);

}

条件变量属性

使用条件变量可以以原子方式阻塞线程,直到某个特定条件为真为止。条件变量始终与互斥锁一起使用。

使用条件变量,线程可以以原子方式阻塞,直到满足某个条件为止。对条件的测试是在互斥锁(互斥)的保护下进行的。

如果条件为假,线程通常会基于条件变量阻塞,并以原子方式释放等待条件变化的互斥锁。如果另一个线程更改了条件,该线程可能会向相关的条件变量发出信号,从而使一个或多个等待的线程执行以下操作:

在以下情况下,条件变量可用于在进程之间同步线程:

调度策略可确定唤醒阻塞线程的方式。对于缺省值 SCHED_OTHER,将按优先级顺序唤醒线程。

必须设置和初始化条件变量的属性,然后才能使用条件变量。表 4–4 列出了用于处理条件变量属性的函数。

表 4–4 条件变量属性

操作

函数说明

初始化条件变量属性 

pthread_condattr_init 语法

删除条件变量属性 

pthread_condattr_destroy 语法

设置条件变量的范围  

pthread_condattr_setpshared 语法

获取条件变量的范围 

pthread_condattr_getpshared 语法

表 4–5 中显示了定义条件变量的范围时 Solaris 线程和 POSIX 线程之间的差异。

表 4–5 条件变量范围比较

Solaris

POSIX

定义

USYNC_PROCESS

PTHREAD_PROCESS_SHARED

用于同步该进程和其他进程中的线程 

USYNC_THREAD

PTHREAD_PROCESS_PRIVATE

用于仅同步该进程中的线程 

初始化条件变量属性

使用 pthread_condattr_init(3C) 可以将与该对象相关联的属性初始化为其缺省值。在执行过程中,线程系统会为每个属性对象分配存储空间。

pthread_condattr_init 语法

int	pthread_condattr_init(pthread_condattr_t *cattr);
#include <pthread.h>

pthread_condattr_t cattr;

int ret;



/* initialize an attribute to default value */

ret = pthread_condattr_init(&cattr); 

调用此函数时,pshared 属性的缺省值为 PTHREAD_PROCESS_PRIVATEpshared 的该值表示可以在进程内使用已初始化的条件变量。

cattr 的数据类型为 opaque,其中包含一个由系统分配的属性对象。cattr 范围可能的值为 PTHREAD_PROCESS_PRIVATEPTHREAD_PROCESS_SHAREDPTHREAD_PROCESS_PRIVATE 是缺省值。

条件变量属性必须首先由 pthread_condattr_destroy(3C) 重新初始化后才能重用。pthread_condattr_init() 调用会返回指向类型为 opaque 的对象的指针。如果未销毁该对象,则会导致内存泄漏。

pthread_condattr_init 返回值

pthread_condattr_init() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。


ENOMEM

描述:

分配的内存不足,无法初始化线程属性对象。


EINVAL

描述:

cattr 指定的值无效。

删除条件变量属性

使用 pthread_condattr_destroy(3C) 可以删除存储并使属性对象无效。

pthread_condattr_destroy 语法

int	pthread_condattr_destroy(pthread_condattr_t *cattr);
#include <pthread.h>

pthread_condattr_t cattr;

int ret;



/* destroy an attribute */

ret

 = pthread_condattr_destroy(&cattr); 

pthread_condattr_destroy 返回值

pthread_condattr_destroy() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。


EINVAL

描述:

cattr 指定的值无效。

设置条件变量的范围

pthread_condattr_setpshared(3C) 可用来将条件变量的范围设置为进程专用(进程内)或系统范围内(进程间)。

pthread_condattr_setpshared 语法

int	pthread_condattr_setpshared(pthread_condattr_t *cattr,

    int pshared);
#include <pthread.h>



pthread_condattr_t cattr;

int ret;



/* all processes */

ret = pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED);



/* within a process */

ret = pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_PRIVATE);

如果 pshared 属性在共享内存中设置为 PTHREAD_PROCESS_SHARED,则其所创建的条件变量可以在多个进程中的线程之间共享。此行为与最初的 Solaris 线程实现中 mutex_init() 中的 USYNC_PROCESS 标志等效。

如果互斥锁的 pshared 属性设置为 PTHREAD_PROCESS_PRIVATE,则仅有那些由同一个进程创建的线程才能够处理该互斥锁。PTHREAD_PROCESS_PRIVATE 是缺省值。PTHREAD_PROCESS_PRIVATE 所产生的行为与在最初的 Solaris 线程的 cond_init() 调用中使用 USYNC_THREAD 标志相同。PTHREAD_PROCESS_PRIVATE 的行为与局部条件变量相同。PTHREAD_PROCESS_SHARED 的行为与全局条件变量等效。

pthread_condattr_setpshared 返回值

pthread_condattr_setpshared() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。


EINVAL

描述:

cattrpshared 的值无效。

获取条件变量的范围

pthread_condattr_getpshared(3C) 可用来获取属性对象 cattrpshared 的当前值。

pthread_condattr_getpshared 语法

int	pthread_condattr_getpshared(const pthread_condattr_t *cattr,

    int *pshared);
#include <pthread.h>



pthread_condattr_t cattr;

int pshared;

int ret;



/* get pshared value of condition variable */

ret = pthread_condattr_getpshared(&cattr, &pshared); 

属性对象的值为 PTHREAD_PROCESS_SHAREDPTHREAD_PROCESS_PRIVATE

pthread_condattr_getpshared 返回值

pthread_condattr_getpshared() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。


EINVAL

描述:

cattr 的值无效。

使用条件变量

本节介绍如何使用条件变量。表 4–6 列出了可用的函数。

表 4–6 条件变量函数

操作

相关函数说明

初始化条件变量 

pthread_cond_init 语法

基于条件变量阻塞 

pthread_cond_wait 语法

解除阻塞特定线程 

pthread_cond_signal 语法

在指定的时间之前阻塞 

pthread_cond_timedwait 语法

在指定的时间间隔内阻塞 

pthread_cond_reltimedwait_np 语法

解除阻塞所有线程 

pthread_cond_broadcast 语法

销毁条件变量状态 

pthread_cond_destroy 语法

初始化条件变量

使用 pthread_cond_init(3C) 可以将 cv 所指示的条件变量初始化为其缺省值,或者指定已经使用 pthread_condattr_init() 设置的条件变量属性。

pthread_cond_init 语法

int	pthread_cond_init(pthread_cond_t *cv,

    const pthread_condattr_t *cattr);
#include <pthread.h>



pthread_cond_t cv;

pthread_condattr_t cattr;

int ret;



/* initialize a condition variable to its default value */

ret = pthread_cond_init(&cv, NULL);



/* initialize a condition variable */

ret = pthread_cond_init(&cv, &cattr); 

cattr 设置为 NULL。将 cattr 设置为 NULL 与传递缺省条件变量属性对象的地址等效,但是没有内存开销。对于 Solaris 线程,请参见cond_init 语法

使用 PTHREAD_COND_INITIALIZER 宏可以将以静态方式定义的条件变量初始化为其缺省属性。PTHREAD_COND_INITIALIZER 宏与动态分配具有 null 属性的 pthread_cond_init() 等效,但是不进行错误检查。

多个线程决不能同时初始化或重新初始化同一个条件变量。如果要重新初始化或销毁某个条件变量,则应用程序必须确保该条件变量未被使用。

pthread_cond_init 返回值

pthread_cond_init() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。


EINVAL

描述:

cattr 指定的值无效。


EBUSY

描述:

条件变量处于使用状态。


EAGAIN

描述:

必要的资源不可用。


ENOMEM

描述:

内存不足,无法初始化条件变量。

基于条件变量阻塞

使用 pthread_cond_wait(3C) 可以以原子方式释放 mp 所指向的互斥锁,并导致调用线程基于 cv 所指向的条件变量阻塞。对于 Solaris 线程,请参见cond_wait 语法

pthread_cond_wait 语法

int	pthread_cond_wait(pthread_cond_t *cv,pthread_mutex_t *mutex);
#include <pthread.h>



pthread_cond_t cv;

pthread_mutex_t mp;

int ret;



/* wait on condition variable */

ret = pthread_cond_wait(&cv, &mp); 

阻塞的线程可以通过 pthread_cond_signal()pthread_cond_broadcast() 唤醒,也可以在信号传送将其中断时唤醒。

不能通过 pthread_cond_wait() 的返回值来推断与条件变量相关联的条件的值的任何变化。必须重新评估此类条件。

pthread_cond_wait() 例程每次返回结果时调用线程都会锁定并且拥有互斥锁,即使返回错误时也是如此。

该条件获得信号之前,该函数一直被阻塞。该函数会在被阻塞之前以原子方式释放相关的互斥锁,并在返回之前以原子方式再次获取该互斥锁。

通常,对条件表达式的评估是在互斥锁的保护下进行的。如果条件表达式为假,线程会基于条件变量阻塞。然后,当该线程更改条件值时,另一个线程会针对条件变量发出信号。这种变化会导致所有等待该条件的线程解除阻塞并尝试再次获取互斥锁。

必须重新测试导致等待的条件,然后才能从 pthread_cond_wait() 处继续执行。唤醒的线程重新获取互斥锁并从 pthread_cond_wait() 返回之前,条件可能会发生变化。等待线程可能并未真正唤醒。建议使用的测试方法是,将条件检查编写为调用 pthread_cond_wait()while() 循环。

    pthread_mutex_lock();

        while(condition_is_false)

            pthread_cond_wait();

    pthread_mutex_unlock();

如果有多个线程基于该条件变量阻塞,则无法保证按特定的顺序获取互斥锁。


注 –

pthread_cond_wait() 是取消点。如果取消处于暂挂状态,并且调用线程启用了取消功能,则该线程会终止,并在继续持有该锁的情况下开始执行清除处理程序。


pthread_cond_wait 返回值

pthread_cond_wait() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。


EINVAL

描述:

cvmp 指定的值无效。

解除阻塞一个线程

对于基于 cv 所指向的条件变量阻塞的线程,使用 pthread_cond_signal(3C) 可以解除阻塞该线程。对于 Solaris 线程,请参见cond_signal 语法

pthread_cond_signal 语法

int	pthread_cond_signal(pthread_cond_t *cv);
#include <pthread.h>



pthread_cond_t cv;

int ret;



/* one condition variable is signaled */

ret = pthread_cond_signal(&cv); 

应在互斥锁的保护下修改相关条件,该互斥锁用于获得信号的条件变量中。否则,可能在条件变量的测试和 pthread_cond_wait() 阻塞之间修改该变量,这会导致无限期等待。

调度策略可确定唤醒阻塞线程的顺序。对于 SCHED_OTHER,将按优先级顺序唤醒线程。

如果没有任何线程基于条件变量阻塞,则调用 pthread_cond_signal() 不起作用。


示例 4–8 使用 pthread_cond_wait()pthread_cond_signal()

pthread_mutex_t count_lock;

pthread_cond_t count_nonzero;

unsigned count;



decrement_count()

{

    pthread_mutex_lock(&count_lock);

    while (count == 0)

        pthread_cond_wait(&count_nonzero, &count_lock);

    count = count - 1;

    pthread_mutex_unlock(&count_lock);

}



increment_count()

{

    pthread_mutex_lock(&count_lock);

    if (count == 0)

        pthread_cond_signal(&count_nonzero);

    count = count + 1;

    pthread_mutex_unlock(&count_lock);

}

pthread_cond_signal 返回值

pthread_cond_signal() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。


EINVAL

描述:

cv 指向的地址非法。

示例 4–8 说明了如何使用 pthread_cond_wait()pthread_cond_signal()

在指定的时间之前阻塞

pthread_cond_timedwait(3C) 的用法与 pthread_cond_wait() 的用法基本相同,区别在于在由 abstime 指定的时间之后 pthread_cond_timedwait() 不再被阻塞。

pthread_cond_timedwait 语法

int	pthread_cond_timedwait(pthread_cond_t *cv,

    pthread_mutex_t *mp, const struct timespec *abstime);
#include <pthread.h>

#include <time.h>



pthread_cond_t cv;

pthread_mutex_t mp;

timestruct_t abstime;

int ret;



/* wait on condition variable */

ret = pthread_cond_timedwait(&cv, &mp, &abstime); 

pthread_cond_timewait() 每次返回时调用线程都会锁定并且拥有互斥锁,即使 pthread_cond_timedwait() 返回错误时也是如此。 对于 Solaris 线程,请参见cond_timedwait 语法

pthread_cond_timedwait() 函数会一直阻塞,直到该条件获得信号,或者最后一个参数所指定的时间已过为止。


注 –

pthread_cond_timedwait() 也是取消点。



示例 4–9 计时条件等待

pthread_timestruc_t to;

pthread_mutex_t m;

pthread_cond_t c;

...

pthread_mutex_lock(&m);

to.tv_sec = time(NULL) + TIMEOUT;

to.tv_nsec = 0;

while (cond == FALSE) {

    err = pthread_cond_timedwait(&c, &m, &to);

    if (err == ETIMEDOUT) {

        /* timeout, do something */

        break;

    }

}

pthread_mutex_unlock(&m);

pthread_cond_timedwait 返回值

pthread_cond_timedwait() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。


EINVAL

描述:

cvabstime 指向的地址非法。


ETIMEDOUT

描述:

abstime 指定的时间已过。

超时会指定为当天时间,以便在不重新计算值的情况下高效地重新测试条件,如示例 4–9 中所示。

在指定的时间间隔内阻塞

pthread_cond_reltimedwait_np(3C) 的用法与 pthread_cond_timedwait() 的用法基本相同,唯一的区别在于 pthread_cond_reltimedwait_np() 会采用相对时间间隔而不是将来的绝对时间作为其最后一个参数的值。

pthread_cond_reltimedwait_np 语法

int  pthread_cond_reltimedwait_np(pthread_cond_t *cv, 

    pthread_mutex_t *mp, 

   const struct timespec *reltime);
#include <pthread.h>

#include <time.h>



pthread_cond_t cv;

pthread_mutex_t mp;

timestruct_t reltime;

int ret;



/* wait on condition variable */

ret = pthread_cond_reltimedwait_np(&cv, &mp, &reltime); 

pthread_cond_reltimedwait_np() 每次返回时调用线程都会锁定并且拥有互斥锁,即使 pthread_cond_reltimedwait_np() 返回错误时也是如此。对于 Solaris 线程,请参见 cond_reltimedwait(3C)pthread_cond_reltimedwait_np() 函数会一直阻塞,直到该条件获得信号,或者最后一个参数指定的时间间隔已过为止。


注 –

pthread_cond_reltimedwait_np() 也是取消点。


pthread_cond_reltimedwait_np 返回值

pthread_cond_reltimedwait_np() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。


EINVAL

描述:

cvreltime 指示的地址非法。


ETIMEDOUT

描述:

reltime 指定的时间间隔已过。

解除阻塞所有线程

对于基于 cv 所指向的条件变量阻塞的线程,使用 pthread_cond_broadcast(3C) 可以解除阻塞所有这些线程,这由 pthread_cond_wait() 来指定。

pthread_cond_broadcast 语法

int	pthread_cond_broadcast(pthread_cond_t *cv);
#include <pthread.h>



pthread_cond_t cv;

int ret;



/* all condition variables are signaled */

ret = pthread_cond_broadcast(&cv); 

如果没有任何线程基于该条件变量阻塞,则调用 pthread_cond_broadcast() 不起作用。对于 Solaris 线程,请参见cond_broadcast 语法

由于 pthread_cond_broadcast() 会导致所有基于该条件阻塞的线程再次争用互斥锁,因此请谨慎使用 pthread_cond_broadcast()。例如,通过使用 pthread_cond_broadcast(),线程可在资源释放后争用不同的资源量,如示例 4–10 中所示。


示例 4–10 条件变量广播

pthread_mutex_t rsrc_lock;

pthread_cond_t rsrc_add;

unsigned int resources;



get_resources(int amount)

{

    pthread_mutex_lock(&rsrc_lock);

    while (resources < amount) {

        pthread_cond_wait(&rsrc_add, &rsrc_lock);

    }

    resources -= amount;

    pthread_mutex_unlock(&rsrc_lock);

}



add_resources(int amount)

{

    pthread_mutex_lock(&rsrc_lock);

    resources += amount;

    pthread_cond_broadcast(&rsrc_add);

    pthread_mutex_unlock(&rsrc_lock);

}

请注意,在 add_resources() 中,首先更新 resources 还是首先在互斥锁中调用 pthread_cond_broadcast() 无关紧要。

应在互斥锁的保护下修改相关条件,该互斥锁用于获得信号的条件变量中。否则,可能在条件变量的测试和 pthread_cond_wait() 阻塞之间修改该变量,这会导致无限期等待。

pthread_cond_broadcast 返回值

pthread_cond_broadcast() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。


EINVAL

描述:

cv 指示的地址非法。

销毁条件变量状态

使用 pthread_cond_destroy(3C) 可以销毁与 cv 所指向的条件变量相关联的任何状态。对于 Solaris 线程,请参见cond_destroy 语法

pthread_cond_destroy 语法

int	pthread_cond_destroy(pthread_cond_t *cv);
#include <pthread.h>



pthread_cond_t cv;

int ret;



/* Condition variable is destroyed */

ret = pthread_cond_destroy(&cv); 

请注意,没有释放用来存储条件变量的空间。

pthread_cond_destroy 返回值

pthread_cond_destroy() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。


EINVAL

描述:

cv 指定的值无效。

唤醒丢失问题

如果线程未持有与条件相关联的互斥锁,则调用 pthread_cond_signal()pthread_cond_broadcast() 会产生唤醒丢失错误。

满足以下所有条件时,即会出现唤醒丢失问题:

仅当修改所测试的条件但未持有与之相关联的互斥锁时,才会出现此问题。只要仅在持有关联的互斥锁同时修改所测试的条件,即可调用 pthread_cond_signal()pthread_cond_broadcast(),而无论这些函数是否持有关联的互斥锁。

生成方和使用者问题

并发编程中收集了许多标准的众所周知的问题,生成方和使用者问题只是其中的一个问题。此问题涉及到一个大小限定的缓冲区和两类线程(生成方和使用者),生成方将项放入缓冲区中,然后使用者从缓冲区中取走项。

生成方必须在缓冲区中有可用空间之后才能向其中放置内容。使用者必须在生成方向缓冲区中写入之后才能从中提取内容。

条件变量表示一个等待某个条件获得信号的线程队列。

示例 4–11 中包含两个此类队列。一个队列 (less) 针对生成方,用于等待缓冲区中出现空位置。另一个队列 (more) 针对使用者,用于等待从缓冲槽位的空位置中提取其中包含的信息。该示例中还包含一个互斥锁,因为描述该缓冲区的数据结构一次只能由一个线程访问。


示例 4–11 生成方和使用者的条件变量问题

typedef struct {

    char buf[BSIZE];

    int occupied;

    int nextin;

    int nextout;

    pthread_mutex_t mutex;

    pthread_cond_t more;

    pthread_cond_t less;

} buffer_t;



buffer_t buffer;

示例 4–12 中所示,生成方线程获取该互斥锁以保护 buffer 数据结构,然后,缓冲区确定是否有空间可用于存放所生成的项。如果没有可用空间,生成方线程会调用 pthread_cond_wait()pthread_cond_wait() 会导致生成方线程连接正在等待 less 条件获得信号的线程队列。less 表示缓冲区中的可用空间。

与此同时,在调用 pthread_cond_wait() 的过程中,该线程会释放互斥锁的锁定。正在等待的生成方线程依赖于使用者线程在条件为真时发出信号,如示例 4–12 中所示。该条件获得信号时,将会唤醒等待 less 的第一个线程。但是,该线程必须再次锁定互斥锁,然后才能从 pthread_cond_wait() 返回。

获取互斥锁可确保该线程再次以独占方式访问缓冲区的数据结构。该线程随后必须检查缓冲区中是否确实存在可用空间。如果空间可用,该线程会向下一个可用的空位置中进行写入。

与此同时,使用者线程可能正在等待项出现在缓冲区中。这些线程正在等待条件变量 more。刚在缓冲区中存储内容的生成方线程会调用 pthread_cond_signal() 以唤醒下一个正在等待的使用者。如果没有正在等待的使用者,此调用将不起作用。

最后,生成方线程会解除锁定互斥锁,从而允许其他线程处理缓冲区的数据结构。


示例 4–12 生成方和使用者问题:生成方

void producer(buffer_t *b, char item)

{

    pthread_mutex_lock(&b->mutex);

   

    while (b->occupied >= BSIZE)

        pthread_cond_wait(&b->less, &b->mutex);



    assert(b->occupied < BSIZE);



    b->buf[b->nextin++] = item;



    b->nextin %= BSIZE;

    b->occupied++;



    /* now: either b->occupied < BSIZE and b->nextin is the index

       of the next empty slot in the buffer, or

       b->occupied == BSIZE and b->nextin is the index of the

       next (occupied) slot that will be emptied by a consumer

       (such as b->nextin == b->nextout) */



    pthread_cond_signal(&b->more);



    pthread_mutex_unlock(&b->mutex);

}

请注意 assert() 语句的用法。除非在编译代码时定义了 NDEBUG,否则 assert() 在其参数的计算结果为真(非零)时将不执行任何操作。如果参数的计算结果为假(零),则该程序会中止。在多线程程序中,此类断言特别有用。如果断言失败,assert() 会立即指出运行时问题。assert() 还有另一个作用,即提供有用的注释。

/* now: either b->occupied ... 开头的注释最好以断言形式表示,但是由于语句过于复杂,无法用布尔值表达式来表示,因此将用英语表示。

断言和注释都是不变量的示例。这些不变量是逻辑语句,在程序正常执行时不应将其声明为假,除非是线程正在修改不变量中提到的一些程序变量时的短暂修改过程中。当然,只要有线程执行语句,断言就应当为真。

使用不变量是一种极为有用的方法。即使没有在程序文本中声明不变量,在分析程序时也应将其视为不变量。

每次线程执行包含注释的代码时,生成方代码中表示为注释的不变量始终为真。如果将此注释移到紧挨 mutex_unlock() 的后面,则注释不一定仍然为真。如果将此注释移到紧跟 assert() 之后的位置,则注释仍然为真。

因此,不变量可用于表示一个始终为真的属性,除非一个生成方或一个使用者正在更改缓冲区的状态。线程在互斥锁的保护下处理缓冲区时,该线程可能会暂时声明不变量为假。但是,一旦线程结束对缓冲区的操作,不变量即会恢复为真。

示例 4–13 给出了使用者的代码。该逻辑流程与生成方的逻辑流程相对称。


示例 4–13 生成方和使用者问题:使用者

char consumer(buffer_t *b)

{

    char item;

    pthread_mutex_lock(&b->mutex);

    while(b->occupied <= 0)

        pthread_cond_wait(&b->more, &b->mutex);



    assert(b->occupied > 0);



    item = b->buf[b->nextout++];

    b->nextout %= BSIZE;

    b->occupied--;



    /* now: either b->occupied > 0 and b->nextout is the index

       of the next occupied slot in the buffer, or

       b->occupied == 0 and b->nextout is the index of the next

       (empty) slot that will be filled by a producer (such as

       b->nextout == b->nextin) */



    pthread_cond_signal(&b->less);

    pthread_mutex_unlock(&b->mutex);



    return(item);

}

使用信号进行同步

信号是 E. W. Dijkstra 在二十世纪六十年代末设计的一种编程架构。Dijkstra 的模型与铁路操作有关:假设某段铁路是单线的,因此一次只允许一列火车通过。

信号将用于同步通过该轨道的火车。火车在进入单一轨道之前必须等待信号灯变为允许通行的状态。火车进入轨道后,会改变信号状态,防止其他火车进入该轨道。火车离开这段轨道时,必须再次更改信号的状态,以便允许其他火车进入轨道。

在计算机版本中,信号以简单整数来表示。线程等待获得许可以便继续运行,然后发出信号,表示该线程已经通过针对信号执行 P 操作来继续运行。

线程必须等到信号的值为正,然后才能通过将信号值减 1 来更改该值。完成此操作后,线程会执行 V 操作,即通过将信号值加 1 来更改该值。这些操作必须以原子方式执行,不能再将其划分成子操作,即,在这些子操作之间不能对信号执行其他操作。在 P 操作中,信号值在减小之前必须为正,从而确保生成的信号值不为负,并且比该值减小之前小 1。

PV 操作中,必须在没有干扰的情况下进行运算。如果针对同一信号同时执行两个 V 操作,则实际结果是信号的新值比原来大 2。

对于大多数人来说,如同记住 Dijkstra 是荷兰人一样,记住 PV 本身的含义并不重要。但是,从真正学术的角度来说,P 代表 prolagen,这是由 proberen te verlagen 演变而来的杜撰词,其意思是尝试减小V 代表 verhogen,其意思是增加。Dijkstra 的技术说明 EWD 74 中介绍了这些含义。

sem_wait(3RT) 和 sem_post(3RT) 分别与 Dijkstra 的 PV 操作相对应。sem_trywait(3RT) 是 P 操作的一种条件形式。如果调用线程不等待就不能减小信号的值,则该调用会立即返回一个非零值。

有两种基本信号:二进制信号和计数信号量。二进制信号的值只能是 0 或 1,计数信号量可以是任意非负值。二进制信号在逻辑上相当于一个互斥锁。

不过,尽管不会强制,但互斥锁应当仅由持有该锁的线程来解除锁定。因为不存在“持有信号的线程”这一概念,所以,任何线程都可以执行 Vsem_post(3RT) 操作。

计数信号量与互斥锁一起使用时的功能几乎与条件变量一样强大。在许多情况下,使用计数信号量实现的代码比使用条件变量实现的代码更为简单,如示例 4–14示例 4–15示例 4–16 中所示。

但是,将互斥锁用于条件变量时,会存在一个隐含的括号。该括号可以清楚表明程序受保护的部分。对于信号则不必如此,可以使用并发编程当中的 go to 对其进行调用。信号的功能强大,但是容易以非结构化的不确定方式使用。

命名信号和未命名信号

POSIX 信号可以是未命名的,也可以是命名的。未命名信号在进程内存中分配,并会进行初始化。未命名信号可能可供多个进程使用,具体取决于信号的分配和初始化的方式。未命名信号可以是通过 fork() 继承的专用信号,也可以通过用来分配和映射这些信号的常规文件的访问保护功能对其进行保护。

命名信号类似于进程共享的信号,区别在于命名信号是使用路径名而非 pshared 值引用的。命名信号可以由多个进程共享。命名信号具有属主用户 ID、组 ID 和保护模式。

对于 openretrievecloseremove 命名信号,可以使用以下函数:sem_opensem_getvaluesem_closesem_unlink。通过使用 sem_open,可以创建一个命名信号,其名称是在文件系统的名称空间中定义的。

有关命名信号的更多信息,请参见 sem_opensem_getvaluesem_closesem_unlink 手册页。

计数信号量概述

从概念上来说,信号量是一个非负整数计数。信号量通常用来协调对资源的访问,其中信号计数会初始化为可用资源的数目。然后,线程在资源增加时会增加计数,在删除资源时会减小计数,这些操作都以原子方式执行。

如果信号计数变为零,则表明已无可用资源。计数为零时,尝试减小信号的线程会被阻塞,直到计数大于零为止。

表 4–7 信号例程

操作

相关函数说明

初始化信号 

sem_init 语法

增加信号 

sem_post 语法

基于信号计数阻塞 

sem_wait 语法

减小信号计数 

sem_trywait 语法

销毁信号状态 

sem_destroy 语法

由于信号无需由同一个线程来获取和释放,因此信号可用于异步事件通知,如用于信号处理程序中。同时,由于信号包含状态,因此可以异步方式使用,而不用象条件变量那样要求获取互斥锁。但是,信号的效率不如互斥锁高。

缺省情况下,如果有多个线程正在等待信号,则解除阻塞的顺序是不确定的。

信号在使用前必须先初始化,但是信号没有属性。

初始化信号

使用 sem_init(3RT) 可以将 sem 所指示的未命名信号变量初始化为 value

sem_init 语法

int	sem_init(sem_t *sem, int pshared, unsigned int value);
#include <semaphore.h>



sem_t sem;

int pshared;

int ret;

int value;



/* initialize a private semaphore */

pshared = 0;

value = 1;

ret = sem_init(&sem, pshared, value); 

如果 pshared 的值为零,则不能在进程之间共享信号。如果 pshared 的值不为零,则可以在进程之间共享信号。对于 Solaris 线程,请参见sema_init 语法

多个线程决不能初始化同一个信号。

不得对其他线程正在使用的信号重新初始化。

初始化进程内信号

pshared 为 0 时,信号只能由该进程内的所有线程使用。

#include <semaphore.h>



sem_t sem;

int ret;

int count = 4;



/* to be used within this process only */

ret = sem_init(&sem, 0, count); 

初始化进程间信号

pshared 不为零时,信号可以由其他进程共享。

#include <semaphore.h>



sem_t sem;

int ret;

int count = 4;



/* to be shared among processes */

ret = sem_init(&sem, 1, count);

sem_init 返回值

sem_init() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。


EINVAL

描述:

参数值超过了 SEM_VALUE_MAX


ENOSPC

描述:

初始化信号所需的资源已经用完。到达信号的 SEM_NSEMS_MAX 限制。


ENOSYS

描述:

系统不支持 sem_init() 函数。


EPERM

描述:

进程缺少初始化信号所需的适当权限。

增加信号

使用 sem_post(3RT) 可以原子方式增加 sem 所指示的信号。

sem_post 语法

int	sem_post(sem_t *sem);
#include <semaphore.h>



sem_t sem;

int ret;



ret = sem_post(&sem); /* semaphore is posted */

如果所有线程均基于信号阻塞,则会对其中一个线程解除阻塞。对于 Solaris 线程,请参见sema_post 语法

sem_post 返回值

sem_post() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。


EINVAL

描述:

sem 所指示的地址非法。

基于信号计数进行阻塞

使用 sem_wait(3RT) 可以阻塞调用线程,直到 sem 所指示的信号计数大于零为止,之后以原子方式减小计数。

sem_wait 语法

int	sem_wait(sem_t *sem);
#include <semaphore.h>



sem_t sem;

int ret;



ret = sem_wait(&sem); /* wait for semaphore */

sem_wait 返回值

sem_wait() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。


EINVAL

描述:

sem 所指示的地址非法。


EINTR

描述:

此函数已被信号中断。

减小信号计数

使用 sem_trywait(3RT) 可以在计数大于零时,尝试以原子方式减小 sem 所指示的信号计数。

sem_trywait 语法

int	sem_trywait(sem_t *sem);
#include <semaphore.h>



sem_t sem;

int ret;



ret = sem_trywait(&sem); /* try to wait for semaphore*/

此函数是 sem_wait() 的非阻塞版本。sem_trywait() 在失败时会立即返回。

sem_trywait 返回值

sem_trywait() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。


EINVAL

描述:

sem 所指示的地址非法。


EINTR

描述:

此函数已被信号中断。


EAGAIN

描述:

信号已为锁定状态,因此该信号不能通过 sem_trywait() 操作立即锁定。

销毁信号状态

使用 sem_destroy(3RT) 可以销毁与 sem 所指示的未命名信号相关联的任何状态。

sem_destroy 语法

int	sem_destroy(sem_t *sem);
#include <semaphore.h>



sem_t sem;

int ret;



ret = sem_destroy(&sem); /* the semaphore is destroyed */

不会释放用来存储信号的空间。对于 Solaris 线程,请参见sema_destroy(3C) 语法

sem_destroy 返回值

sem_destroy() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。


EINVAL

描述:

sem 所指示的地址非法。

使用信号时的生成方和使用者问题

示例 4–14 中的数据结构与示例 4–11 中所示的用于条件变量示例的结构类似。两个信号分别表示空缓冲区和满缓冲区的数目,通过这些信号可确保生成方等待缓冲区变空,使用者等待缓冲区变满为止。


示例 4–14 使用信号时的生成方和使用者问题

typedef struct {

    char buf[BSIZE];

    sem_t occupied;

    sem_t empty;

    int nextin;

    int nextout;

    sem_t pmut;

    sem_t cmut;

} buffer_t;



buffer_t buffer;



sem_init(&buffer.occupied, 0, 0);

sem_init(&buffer.empty,0, BSIZE);

sem_init(&buffer.pmut, 0, 1);

sem_init(&buffer.cmut, 0, 1);

buffer.nextin = buffer.nextout = 0;

另一对二进制信号与互斥锁作用相同。在多个生成方使用多个空缓冲槽位,以及多个使用者使用多个满缓冲槽位的情况下,信号可用来控制对缓冲区的访问。在这种情况下,使用互斥锁可能会更好,但这里主要是为了演示信号的用法。


示例 4–15 生成方和使用者问题:生成方

void producer(buffer_t *b, char item) {

    sem_wait(&b->empty);

    sem_wait(&b->pmut);



    b->buf[b->nextin] = item;

    b->nextin++;

    b->nextin %= BSIZE;



    sem_post(&b->pmut);

    sem_post(&b->occupied);

}


示例 4–16 生成方和使用者问题:使用者

char consumer(buffer_t *b) {

    char item;



    sem_wait(&b->occupied);

   

    sem_wait(&b->cmut);



    item = b->buf[b->nextout];

    b->nextout++;

    b->nextout %= BSIZE;



    sem_post(&b->cmut);



    sem_post(&b->empty);



    return(item);

}

读写锁属性

通过读写锁,可以对受保护的共享资源进行并发读取和独占写入。读写锁是可以在读取写入模式下锁定的单一实体。要修改资源,线程必须首先获取互斥写锁。必须释放所有读锁之后,才允许使用互斥写锁。

有关 Solaris 线程所实现的读写锁,请参见相似的同步函数-读写锁

对数据库的访问可以使用读写锁进行同步。读写锁支持并发读取数据库记录,因为读操作不会更改记录的信息。要更新数据库时,写操作必须获取互斥写锁。

要更改缺省的读写锁属性,可以声明和初始化属性对象。通常,可以在应用程序开头的某个位置设置读写锁属性,设置在应用程序的起始位置可使属性更易于查找和修改。下表列出了本节中讨论的用来处理读写锁属性的函数。

表 4–8 读写锁属性例程

操作

相关函数说明

初始化读写锁属性 

pthread_rwlockattr_init 语法

销毁读写锁属性 

pthread_rwlockattr_destroy 语法

设置读写锁属性 

pthread_rwlockattr_setpshared 语法

获取读写锁属性 

pthread_rwlockattr_getpshared 语法

初始化读写锁属性

pthread_rwlockattr_init(3C) 使用实现中定义的所有属性的缺省值来初始化读写锁属性对象 attr

pthread_rwlockattr_init 语法

#include <pthread.h>



int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);

如果调用 pthread_rwlockattr_init 来指定已初始化的读写锁属性对象,则结果是不确定的。读写锁属性对象初始化一个或多个读写锁之后,影响该对象的任何函数(包括销毁)不会影响先前已初始化的读写锁。

pthread_rwlockattr_init 返回值

如果成功,pthread_rwlockattr_init() 会返回零。否则,将返回用于指明错误的错误号。


ENOMEM

描述:

内存不足,无法初始化读写锁属性对象。

销毁读写锁属性

pthread_rwlockattr_destroy(3C) 可用来销毁读写锁属性对象。

pthread_rwlockattr_destroy 语法

#include <pthread.h>



int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);

在再次调用 pthread_rwlockattr_init() 重新初始化该对象之前,使用该对象所产生的影响是不确定的。实现可以导致 pthread_rwlockattr_destroy()attr 所引用的对象设置为无效值。

pthread_rwlockattr_destroy 返回值

如果成功,pthread_rwlockattr_destroy() 会返回零。否则,将返回用于指明错误的错误号。


EINVAL

描述:

attr 指定的值无效。

设置读写锁属性

pthread_rwlockattr_setpshared(3C) 可用来设置由进程共享的读写锁属性。

pthread_rwlockattr_setpshared 语法

#include <pthread.h>



int pthread_rwlockattr_setpshared(pthread_rwlockattr_t  *attr, 

											int  pshared);

读写锁属性可以为以下值之一:


PTHREAD_PROCESS_SHARED

描述:

允许可访问用于分配读写锁的内存的任何线程对读写锁进行处理。即使该锁是在由多个进程共享的内存中分配的,也允许对其进行处理。


PTHREAD_PROCESS_PRIVATE

描述:

读写锁只能由某些线程处理,这些线程与初始化该锁的线程在同一进程中创建。如果不同进程的线程尝试对此类读写锁进行处理,则其行为是不确定的。由进程共享的属性的缺省值为 PTHREAD_PROCESS_PRIVATE

pthread_rwlockattr_setpshared 返回值

如果成功,pthread_rwlockattr_setpshared() 会返回零。否则,将返回用于指明错误的错误号。


EINVAL

描述:

attrpshared 指定的值无效。

获取读写锁属性

pthread_rwlockattr_getpshared(3C) 可用来获取由进程共享的读写锁属性。

pthread_rwlockattr_getpshared 语法

#include <pthread.h>



int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t  *attr, 

											int *pshared);

pthread_rwlockattr_getpshared()attr 引用的已初始化属性对象中获取由进程共享的属性的值。

pthread_rwlockattr_getpshared 返回值

如果成功,pthread_rwlockattr_getpshared() 会返回零。否则,将返回用于指明错误的错误号。


EINVAL

描述:

attrpshared 指定的值无效。

使用读写锁

配置读写锁的属性之后,即可初始化读写锁。以下函数用于初始化或销毁读写锁、锁定或解除锁定读写锁或尝试锁定读写锁。下表列出了本节中讨论的用来处理读写锁的函数。

表 4–9 处理读写锁的例程

操作

相关函数说明

初始化读写锁 

pthread_rwlock_init 语法

读取读写锁中的锁 

pthread_rwlock_rdlock 语法

读取非阻塞读写锁中的锁 

pthread_rwlock_tryrdlock 语法

写入读写锁中的锁 

pthread_rwlock_wrlock 语法

写入非阻塞读写锁中的锁 

pthread_rwlock_trywrlock 语法

解除锁定读写锁 

pthread_rwlock_unlock 语法

销毁读写锁 

pthread_rwlock_destroy 语法

初始化读写锁

使用 pthread_rwlock_init(3C) 可以通过 attr 所引用的属性初始化 rwlock 所引用的读写锁。

pthread_rwlock_init 语法

#include <pthread.h>



int pthread_rwlock_init(pthread_rwlock_t *rwlock, 

								const pthread_rwlockattr_t *attr);



pthread_rwlock_t  rwlock = PTHREAD_RWLOCK_INITIALIZER;

如果 attrNULL,则使用缺省的读写锁属性,其作用与传递缺省读写锁属性对象的地址相同。初始化读写锁之后,该锁可以使用任意次数,而无需重新初始化。成功初始化之后,读写锁的状态会变为已初始化和未锁定。如果调用 pthread_rwlock_init() 来指定已初始化的读写锁,则结果是不确定的。如果读写锁在使用之前未初始化,则结果是不确定的。对于 Solaris 线程,请参见rwlock_init 语法

如果缺省的读写锁属性适用,则 PTHREAD_RWLOCK_INITIALIZER 宏可初始化以静态方式分配的读写锁,其作用与通过调用 pthread_rwlock_init() 并将参数 attr 指定为 NULL 进行动态初始化等效,区别在于不会执行错误检查。

pthread_rwlock_init 返回值

如果成功,pthread_rwlock_init() 会返回零。否则,将返回用于指明错误的错误号。

如果 pthread_rwlock_init() 失败,将不会初始化 rwlock,并且 rwlock 的内容是不确定的。


EINVAL

描述:

attrrwlock 指定的值无效。

获取读写锁中的读锁

pthread_rwlock_rdlock(3C) 可用来向 rwlock 所引用的读写锁应用读锁。

pthread_rwlock_rdlock 语法

#include <pthread.h>



int  pthread_rwlock_rdlock(pthread_rwlock_t *rwlock );

如果写入器未持有读锁,并且没有任何写入器基于该锁阻塞,则调用线程会获取读锁。如果写入器未持有读锁,但有多个写入器正在等待该锁时,调用线程是否能获取该锁是不确定的。如果某个写入器持有读锁,则调用线程无法获取该锁。如果调用线程未获取读锁,则它将阻塞。调用线程必须获取该锁之后,才能从 pthread_rwlock_rdlock() 返回。如果在进行调用时,调用线程持有 rwlock 中的写锁,则结果是不确定的。

为避免写入器资源匮乏,允许在多个实现中使写入器的优先级高于读取器。例如,Solaris 线程实现中写入器的优先级高于读取器。 请参见rw_rdlock 语法

一个线程可以在 rwlock 中持有多个并发的读锁,该线程可以成功调用 pthread_rwlock_rdlock() n 次。该线程必须调用 pthread_rwlock_unlock() n 次才能执行匹配的解除锁定操作。

如果针对未初始化的读写锁调用 pthread_rwlock_rdlock(),则结果是不确定的。

线程信号处理程序可以处理传送给等待读写锁的线程的信号。从信号处理程序返回后,线程将继续等待读写锁以执行读取,就好像线程未中断一样。

pthread_rwlock_rdlock 返回值

如果成功,pthread_rwlock_rdlock() 会返回零。否则,将返回用于指明错误的错误号。


EINVAL

描述:

attrrwlock 指定的值无效。

读取非阻塞读写锁中的锁

pthread_rwlock_tryrdlock(3C) 应用读锁的方式与 pthread_rwlock_rdlock() 类似,区别在于如果任何线程持有 rwlock 中的写锁或者写入器基于 rwlock 阻塞,则 pthread_rwlock_tryrdlock() 函数会失败。对于 Solaris 线程,请参见rw_tryrdlock 语法

pthread_rwlock_tryrdlock 语法

#include <pthread.h>



int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

pthread_rwlock_tryrdlock 返回值

如果获取了用于在 rwlock 所引用的读写锁对象中执行读取的锁,则 pthread_rwlock_tryrdlock() 将返回零。如果没有获取该锁,则返回用于指明错误的错误号。


EBUSY

描述:

无法获取读写锁以执行读取,因为写入器持有该锁或者基于该锁已阻塞。

写入读写锁中的锁

pthread_rwlock_wrlock(3C) 可用来向 rwlock 所引用的读写锁应用写锁。

pthread_rwlock_wrlock 语法

#include <pthread.h>



int  pthread_rwlock_wrlock(pthread_rwlock_t *rwlock );

如果没有其他读取器线程或写入器线程持有读写锁 rwlock,则调用线程将获取写锁。否则,调用线程将阻塞。调用线程必须获取该锁之后,才能从 pthread_rwlock_wrlock() 调用返回。如果在进行调用时,调用线程持有读写锁(读锁或写锁),则结果是不确定的。

为避免写入器资源匮乏,允许在多个实现中使写入器的优先级高于读取器。(例如,Solaris 线程实现允许写入器的优先级高于读取器。请参见rw_wrlock 语法。)

如果针对未初始化的读写锁调用 pthread_rwlock_wrlock(),则结果是不确定的。

线程信号处理程序可以处理传送给等待读写锁以执行写入的线程的信号。从信号处理程序返回后,线程将继续等待读写锁以执行写入,就好像线程未中断一样。

pthread_rwlock_wrlock 返回值

如果获取了用于在 rwlock 所引用的读写锁对象中执行写入的锁,则 pthread_rwlock_rwlock() 将返回零。如果没有获取该锁,则返回用于指明错误的错误号。

写入非阻塞读写锁中的锁

pthread_rwlock_trywrlock(3C) 应用写锁的方式与 pthread_rwlock_wrlock() 类似,区别在于如果任何线程当前持有用于读取和写入的 rwlock,则 pthread_rwlock_trywrlock() 函数会失败。对于 Solaris 线程,请参见 rw_trywrlock 语法

pthread_rwlock_trywrlock 语法

#include <pthread.h>



int pthread_rwlock_trywrlock(pthread_rwlock_t  *rwlock);

如果针对未初始化的读写锁调用 pthread_rwlock_trywrlock(),则结果是不确定的。

线程信号处理程序可以处理传送给等待读写锁以执行写入的线程的信号。从信号处理程序返回后,线程将继续等待读写锁以执行写入,就好像线程未中断一样。

pthread_rwlock_trywrlock 返回值

如果获取了用于在 rwlock 引用的读写锁对象中执行写入的锁,则 pthread_rwlock_trywrlock() 将返回零。否则,将返回用于指明错误的错误号。


EBUSY

描述:

无法为写入获取读写锁,因为已为读取或写入锁定该读写锁。

解除锁定读写锁

pthread_rwlock_unlock(3C) 可用来释放在 rwlock 引用的读写锁对象中持有的锁。

pthread_rwlock_unlock 语法

#include <pthread.h>



int pthread_rwlock_unlock (pthread_rwlock_t  *rwlock);

如果调用线程未持有读写锁 rwlock,则结果是不确定的。对于 Solaris 线程,请参见rw_unlock 语法

如果通过调用 pthread_rwlock_unlock() 来释放读写锁对象中的读锁,并且其他读锁当前由该锁对象持有,则该对象会保持读取锁定状态。如果 pthread_rwlock_unlock() 释放了调用线程在该读写锁对象中的最后一个读锁,则调用线程不再是该对象的属主。如果 pthread_rwlock_unlock() 释放了该读写锁对象的最后一个读锁,则该读写锁对象将处于无属主、解除锁定状态。

如果通过调用 pthread_rwlock_unlock() 释放了该读写锁对象的最后一个写锁,则该读写锁对象将处于无属主、解除锁定状态。

如果 pthread_rwlock_unlock() 解除锁定该读写锁对象,并且多个线程正在等待获取该对象以执行写入,则通过调度策略可确定获取该对象以执行写入的线程。如果多个线程正在等待获取读写锁对象以执行读取,则通过调度策略可确定等待线程获取该对象以执行写入的顺序。如果多个线程基于 rwlock 中的读锁和写锁阻塞,则无法确定读取器和写入器谁先获得该锁。

如果针对未初始化的读写锁调用 pthread_rwlock_unlock(),则结果是不确定的。

pthread_rwlock_unlock 返回值

如果成功,pthread_rwlock_unlock() 会返回零。否则,将返回用于指明错误的错误号。

销毁读写锁

pthread_rwlock_destroy(3C) 可用来销毁 rwlock 引用的读写锁对象并释放该锁使用的任何资源。

pthread_rwlock_destroy 语法

#include <pthread.h>



int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);



pthread_rwlock_t  rwlock = PTHREAD_RWLOCK_INITIALIZER;

在再次调用 pthread_rwlock_init() 重新初始化该锁之前,使用该锁所产生的影响是不确定的。实现可能会导致 pthread_rwlock_destroy()rwlock 所引用的对象设置为无效值。如果在任意线程持有 rwlock 时调用 pthread_rwlock_destroy(),则结果是不确定的。尝试销毁未初始化的读写锁会产生不确定的行为。已销毁的读写锁对象可以使用 pthread_rwlock_init() 来重新初始化。销毁读写锁对象之后,如果以其他方式引用该对象,则结果是不确定的。对于 Solaris 线程,请参见rwlock_destroy 语法

pthread_rwlock_destroy 返回值

如果成功,pthread_rwlock_destroy() 会返回零。否则,将返回用于指明错误的错误号。


EINVAL

描述:

attrrwlock 指定的值无效。

跨进程边界同步

每个同步元语都可以跨进程边界使用。通过确保同步变量位于共享内存段中,并调用适当的 init() 例程,可设置元语。元语必须已经初始化,并且其共享属性设置为在进程间使用。

生成方和使用者问题示例

示例 4–17 说明了位于不同进程中的生成方和使用者的问题。主例程将与其子进程共享的全零内存段映射到其地址空间。

创建子进程是为了运行使用者,父进程则运行生成方。

本示例还说明了生成方和使用者的驱动程序。producer_driver() 可从 stdin 读取字符并调用 producer()consumer_driver() 通过调用 consumer() 来获取字符并将这些字符写入 stdout 中。

示例 4–17 中的数据结构与示例 4–4 中所示用于条件变量示例的结构类似。两个信号分别空缓冲区和满缓冲区的数量,通过这些信号可确保生成方等待缓冲区变空,使用者等待缓冲区变满为止。


示例 4–17 跨进程边界同步

main() {

    int zfd;

    buffer_t *buffer;

    pthread_mutexattr_t mattr;

    pthread_condattr_t cvattr_less, cvattr_more;



    zfd = open("/dev/zero", O_RDWR);

    buffer = (buffer_t *)mmap(NULL, sizeof(buffer_t),

        PROT_READ|PROT_WRITE, MAP_SHARED, zfd, 0);

    buffer->occupied = buffer->nextin = buffer->nextout = 0;



    pthread_mutex_attr_init(&mattr);

    pthread_mutexattr_setpshared(&mattr,

        PTHREAD_PROCESS_SHARED);



    pthread_mutex_init(&buffer->lock, &mattr);

    pthread_condattr_init(&cvattr_less);

    pthread_condattr_setpshared(&cvattr_less, PTHREAD_PROCESS_SHARED);

    pthread_cond_init(&buffer->less, &cvattr_less);

    pthread_condattr_init(&cvattr_more);

    pthread_condattr_setpshared(&cvattr_more,   

        PTHREAD_PROCESS_SHARED);

    pthread_cond_init(&buffer->more, &cvattr_more);



    if (fork() == 0)

        consumer_driver(buffer);

    else

        producer_driver(buffer);

}



void producer_driver(buffer_t *b) {

    int item;



    while (1) {

        item = getchar();

        if (item == EOF) {

            producer(b, `\0');

            break;

        } else

            producer(b, (char)item);

    }

}



void consumer_driver(buffer_t *b) {

    char item;



    while (1) {

        if ((item = consumer(b)) == '\0')

            break;

        putchar(item);

    }

}

比较元语

线程中最基本的同步元语是互斥锁。因此,在内存使用和执行时间这两个方面,互斥锁都是最高效的机制。互斥锁的基本用途是按顺序访问资源。

线程中第二高效的元语是条件变量。条件变量的基本用途是基于状态的变化进行阻塞。条件变量可提供线程等待功能。请注意,线程在基于条件变量阻塞之前必须首先获取互斥锁,在从 pthread_cond_wait() 返回之后必须解除锁定互斥锁。线程还必须在状态发生改变期间持有互斥锁,然后才能对 pthread_cond_signal() 进行相应的调用。

信号比条件变量占用更多内存。由于信号变量基于状态而非控制来工作,因此在某些情况下更易于使用。与锁不同,信号没有属主。任何线程都可以增加已阻塞的信号。

通过读写锁,可以对受保护的资源进行并发读取和独占写入。读写锁是可以在读取写入模式下锁定的单一实体。要修改资源,线程必须首先获取互斥写锁。必须释放所有读锁之后,才允许使用互斥写锁。