互斥变量是用 pthread_mutex_t 数据类型表示的。下面几个函数可操作互斥量。
使用互斥量前必须先对它进行初始化,可把它设置为常量 PTHREAD_MUTEX_INITIALIZER(只适用于静态分配的互斥量),也可以通过 pthread_mutex_init 函数进行初始化。要用默认的属性初始化互斥量,只需把 pthread_mutex_init 的参数 attr 设为 NULL。如果互斥量是动态分配的,则释放内存前需要调用 pthread_mutex_destroy。
函数 pthread_mutex_lock 和 pthread_mutex_unlock 可分别对互斥量进行加锁和解锁。如果互斥量已经上锁,调用线程将阻塞直到互斥量被解锁。如果线程不希望被阻塞,可以使用 pthread_mutex_trylock 尝试对互斥量加锁。若调用 pthread_mutex_trylock 时互斥量未上锁,则该函数就锁住互斥量,然后直接返回 0,否则该函数就会失败,然后返回 EBUSY。
下面这段代码描述了如何用互斥量来保护某个数据结构。当有多个线程要访问动态分配的对象时,可以在对象中嵌入引用计数,确保在所有使用该对象的线程完成数据访问之前,该对象的内存空间不会被释放。
为避免线程在获取锁时永久阻塞,可以使用函数 pthread_mutex_timedlock。
该函数与 pthread_mutex_lock 基本等价,但它允许绑定线程阻塞时间。当达到超时时间时,它不会对互斥量进行加锁,而是返回错误码 ETIMEDOUT。这里的时间参数是一个绝对时间。
下面这个示例通过对已有的互斥量进行再次加锁(这通常会造成死锁)来演示了如何用 pthread_mutex_timedlock 来避免永久阻塞。
编译后运行:
说完互斥量,接下来再说说读写锁。
读写锁也称共享互斥锁,它能在读取数据频率远高于写数据时提供比互斥量更高的并发性。当读写锁在写模式下时,它所保护的数据就可以被安全地修改,因为一次只有一个线程可以拥有写模式的锁。而当它在读模式下时,只要线程先获取了读模式下的读写锁,该锁所保护的数据就可以同时被多个获得读模式锁的线程读取。为避免读模式锁长期占用,一般如果有一个线程试图以写模式获取锁时,读写锁通常会阻塞随后的读模式锁请求。
与互斥量相同,读写锁在使用前必须初始化,在释放其底层内存前必须销毁。
attr 参数是读写锁的属性,为 NULL 表示使用默认属性。XSI 扩展中也定义了 PTHREAD_RWLOCK_INITIALIZER 常量,用来对静态分配的读写锁进行初始化。
可以调用 pthread_rwlock_rdlock 和 pthread_rwlock_wrlock 分别在读模式和写模式下锁定读写锁,这两种方式都可以调用 pthread_rwlock_unlock 来进行解锁。
各种实现可能会对共享模式下可获取的读写锁的次数进行限制,所以应该检查 pthread_rwlock_rdlock 的返回值。
Single UNIX Specification 还定义了读写锁原语的条件版本和带有超时的读写锁函数。
可以获取锁时,前两个函数返回 0,否则,它们返回错误 EBUSY。后两个函数如果在到达指定的时刻时还不能获取锁就将返回 ETIMEDOUT 错误。
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
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);
/* 所有函数的返回值:若成功,返回 0;否则,返回错误编号 */
使用互斥量前必须先对它进行初始化,可把它设置为常量 PTHREAD_MUTEX_INITIALIZER(只适用于静态分配的互斥量),也可以通过 pthread_mutex_init 函数进行初始化。要用默认的属性初始化互斥量,只需把 pthread_mutex_init 的参数 attr 设为 NULL。如果互斥量是动态分配的,则释放内存前需要调用 pthread_mutex_destroy。
函数 pthread_mutex_lock 和 pthread_mutex_unlock 可分别对互斥量进行加锁和解锁。如果互斥量已经上锁,调用线程将阻塞直到互斥量被解锁。如果线程不希望被阻塞,可以使用 pthread_mutex_trylock 尝试对互斥量加锁。若调用 pthread_mutex_trylock 时互斥量未上锁,则该函数就锁住互斥量,然后直接返回 0,否则该函数就会失败,然后返回 EBUSY。
下面这段代码描述了如何用互斥量来保护某个数据结构。当有多个线程要访问动态分配的对象时,可以在对象中嵌入引用计数,确保在所有使用该对象的线程完成数据访问之前,该对象的内存空间不会被释放。
#include <stdlib.h>
#include <pthread.h>
struct foo{
int f_id;
int f_count;
pthread_mutex_t f_lock;
/*...more stuff here...*/
};
struct foo* foo_alloc(int id){ // allocate the object
struct foo *foop;
if((foop=molloc(sizeof(struct foo))) != NULL){
foop->f_id = id;
foop->f_count = 1;
if(pthread_mutex_init(&foop->f_lock, NULL) != 0){
free(foop);
return NULL
}
/*...continue initialization...*/
}
return foop;
}
void foo_hold(struct foo *foop){ // add a reference to the object
pthread_mutex_lock(&foop->f_lock);
foop->f_count++;
pthread_mutex_unlock(&foop->f_lock);
}
void foo_rele(struct foo *foop){ // release a reference to the object
pthread_mutex_lock(&foop->f_lock);
if(--foop->f_count == 0){
pthread_mutex_unlock(&foop->f_lock);
pthread_mutex_destroy(&foop->f_lock);
free(foop);
}else{
pthread_mutex_unlock(&foop->f_lock);
}
}
为避免线程在获取锁时永久阻塞,可以使用函数 pthread_mutex_timedlock。
#include <pthread.h>
#include <time.h>
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict tsptr);
/* 返回值:若成功,返回 0;否则,返回错误编号 */
该函数与 pthread_mutex_lock 基本等价,但它允许绑定线程阻塞时间。当达到超时时间时,它不会对互斥量进行加锁,而是返回错误码 ETIMEDOUT。这里的时间参数是一个绝对时间。
下面这个示例通过对已有的互斥量进行再次加锁(这通常会造成死锁)来演示了如何用 pthread_mutex_timedlock 来避免永久阻塞。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
void pr_time(struct timespec tout){
char buf[64];
struct tm *tmp = localtime(&tout.tv_sec);
strftime(buf, sizeof(buf), "%r", tmp);
printf("current time is: %s\n", buf);
}
int main(void){
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&lock);
printf("mutex is locked\n");
struct timespec tout;
clock_gettime(CLOCK_REALTIME, &tout);
tout.tv_sec += 5; // 5 seconds from now
// caution: this could lead to deadlock
int err = pthread_mutex_timedlock(&lock, &tout);
clock_gettime(CLOCK_REALTIME, &tout);
pr_time(tout);
pr_time(tout);
if(err != 0)
printf("can't lock mutex again: %s\n", strerror(err));
else
printf("mutex locked again!\n");
exit(0);
}
编译后运行:
$ gcc timeoutLock.c -lpthread -lrt -o timeoutLock.out
$ ./timeoutLock.out
mutex is locked
current time is: 10:37:07 AM
current time is: 10:37:12 AM
can't lock mutex again: Connection timed out
说完互斥量,接下来再说说读写锁。
读写锁也称共享互斥锁,它能在读取数据频率远高于写数据时提供比互斥量更高的并发性。当读写锁在写模式下时,它所保护的数据就可以被安全地修改,因为一次只有一个线程可以拥有写模式的锁。而当它在读模式下时,只要线程先获取了读模式下的读写锁,该锁所保护的数据就可以同时被多个获得读模式锁的线程读取。为避免读模式锁长期占用,一般如果有一个线程试图以写模式获取锁时,读写锁通常会阻塞随后的读模式锁请求。
与互斥量相同,读写锁在使用前必须初始化,在释放其底层内存前必须销毁。
#include <pthread.h>
int pthread_rwlock_init( pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock *rwlock);
/* 两个函数返回值:若成功,返回 0;否则,返回错误编号 */
attr 参数是读写锁的属性,为 NULL 表示使用默认属性。XSI 扩展中也定义了 PTHREAD_RWLOCK_INITIALIZER 常量,用来对静态分配的读写锁进行初始化。
可以调用 pthread_rwlock_rdlock 和 pthread_rwlock_wrlock 分别在读模式和写模式下锁定读写锁,这两种方式都可以调用 pthread_rwlock_unlock 来进行解锁。
#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
/* 三个函数返回值:若成功,返回 0;否则,返回错误编号 */
各种实现可能会对共享模式下可获取的读写锁的次数进行限制,所以应该检查 pthread_rwlock_rdlock 的返回值。
Single UNIX Specification 还定义了读写锁原语的条件版本和带有超时的读写锁函数。
#include <pthread.h>
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);
/* 所有函数返回值:若成功,返回 0;否则,返回错误编号 */
可以获取锁时,前两个函数返回 0,否则,它们返回错误 EBUSY。后两个函数如果在到达指定的时刻时还不能获取锁就将返回 ETIMEDOUT 错误。