61 读书笔记:第12章 线程控制 (2)

12.4 同步属性

1.互斥量属性

        用pthread_mutexattr_init初始化pthread_mutexattr_t结构,用pthread_mutexattr_destroy来对结构进行回收。

       #include <pthread.h>
       int pthread_mutexattr_init(pthread_mutexattr_t *attr);
       int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
// 两个函数返回值:若成功则返回0,否则返回错误编号

        pthread_mutexattr_init函数用默认的互斥量属性初始化pthread_mutexattr_t结构。值得注意的两个属性是进程共享属性和类型属性。


        在进程中,多个线程可以访问同一个同步对象,这是默认的行为。在这种情况下,进程共享互斥量属性需要设置为PTHREAD_PROCESS_PRIVATE。

        存在这种机制,运行相互独立的多个进程把同一个内存区域映射到它们各自独立的地址空间。就像多个线程访问共享数据一样,多个进程访问共享数据通常也需要同步。如果进程共享互斥量属性设置为PTHREAD_PROCESS_SHARED,从多个进程共享的内存区域中分配的互斥量就可以用于这些进程的同步。

        可以使用pthread_mutexattr_getpshared函数查询pthread_mutexattr_t结构,得到它的进程共享属性,可以用pthread_mutexattr_setpshared函数修改进程共享属性。

       #include <pthread.h>
       int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared);
       int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr,int pshared);
// 两个函数的返回值:若成功则返回0,否则返回错误编号

        进程共享互斥量属性设置为PTHREAD_PROCESS_PRIVATE时,允许pthread线程库提供更加有效的互斥量实现,这在多线程应用程序中是默认的情况。在多个进程共享多个互斥量的情况下,pthread线程库可以限制开销较大的互斥量实现。


        类型互斥量属性控制着互斥量类型。POSIX.1定义了四种类型。

        PTHREAD_MUTEX_NORMAL类型是标准的互斥量类型,并不做任何特殊的错误检查或死锁检测。PTHREAD_MUTEX_ERRORCHECK互斥量类型提供错误检查。PTHREAD_MUTEX_RECURSIVE互斥量类型允许同一线程在互斥量解锁之前对该互斥量进行多次加锁。用一个递归互斥量维护锁的计数,在解锁和加锁次数不相同的情况下不会释放锁。PTHREAD_MUTEX_DEFAULT类型可以用于请求默认语义,操作系统在实现它的时候可以把这种类型自由地映射到其他类型。

        可以用pthread_mutexattr_gettype函数得到互斥量类型属性,用pthread_mutexattr_settype函数修改互斥量类型属性。

      #include <pthread.h>
       int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr,int *restrict type);
       int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);
// 返回值:若成功则返回0,否则返回错误编号

        互斥量用于保护与条件变量关联的条件。在阻塞线程之前pthread_cond_wait和pthread_cond_timedwait函数释放与条件相关的互斥量,这就允许其他线程获取互斥量、改变条件、释放互斥量并向条件变量发送信号。既然改变条件时必须占有互斥量,所以使用递归互斥量并不是好的办法。如果递归互斥量被多次加锁,然后用在调用pthread_cond_wait函数中,那么条件永远都不会得到满足,因为pthread_cond_wait所做的解锁操作并不能释放互斥量。

        如果需要把现有的单线程接口放到多线程环境中,递归互斥量非常有用,但由于程序兼容性限制,不能对函数接口进行修改。然而由于递归锁的使用需要一定技巧,它只应在没有其他可行方案的情况下使用。


        程序清单12-2解释了有必要使用递归互斥量的另一种情况。这里,有一个“超时”函数,它允许另一个函数可以安排在未来的某个时间允许。假设线程并不是很昂贵的资源,可以为每个未决的超时函数创建一个线程。线程在时间未到时将一直等待,时间到了以后就调用请求的函数。

        《UNIX环境高级编程》P321:程序清单12-2 使用递归互斥量

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>
#include <sys/time.h>
#include <errno.h>
#include <string.h>

struct to_info {
	void			(*to_fn)(void *);	// 函数
	void			*to_arg;			// 参数
	struct timespec	to_wait;			// 等待时间
};

int makethread(void *(*fn)(void *), void *arg)
{
	int				err;
	pthread_t		tid;
	pthread_attr_t	attr;

	// 初始化线程属性结构pthread_attr_t
	err = pthread_attr_init(&attr);	
	if (err != 0)
		return(err);
	
	// 修改pthread_attr_t结构中的detachstate:以分离状态启动线程
	err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
	if (err == 0)
		err = pthread_create(&tid, &attr, fn, arg);	// 创建线程

	pthread_attr_destroy(&attr);					// 对pthread_attr_t去初始化
	return(err);
}

#define SECTONSEC	1000000000	// 秒到纳秒
#define USECTONSEC	1000		// 微秒到纳秒

void *timeout_helper(void *arg)
{
	struct to_info	*tip;

	tip = (struct to_info *)arg;
	nanosleep(&tip->to_wait, NULL);		// 程序休眠
	(*tip->to_fn)(tip->to_arg);			// 调用函数
	return(0);
}

void timeout(const struct timespec *when, void (*func)(void *), void *arg)
{
	struct timespec	now;
	struct timeval	tv;
	struct to_info	*tip;
	int				err;

	gettimeofday(&tv, NULL);
	now.tv_sec	= tv.tv_sec;
	now.tv_nsec	= tv.tv_usec * USECTONSEC;	// 微秒到纳秒
	
	if ((when->tv_sec > now.tv_sec) ||		// 传入时间大于当前时间值
		   	(when->tv_sec == now.tv_sec && when->tv_nsec > now.tv_nsec)) {
		tip = malloc(sizeof(struct to_info));
		if (tip != NULL) {
			tip->to_fn	= func;
			tip->to_arg	= arg;
			tip->to_wait.tv_sec	= when->tv_sec - now.tv_sec;
			if (when->tv_nsec >= now.tv_nsec) {
				tip->to_wait.tv_nsec = when->tv_nsec - now.tv_nsec;
			} else {
				tip->to_wait.tv_sec--;
				tip->to_wait.tv_nsec = SECTONSEC - now.tv_nsec + when->tv_nsec;
			}
			err = makethread(timeout_helper, (void *)tip);	// 创建新线程
			if (err == 0)
				return;
		}
	}
	// 如果传入时间小于当前时间或者malloc失败,就不能创建新线程,因此我们只是调用函数
	(*func)(arg);
}

pthread_mutexattr_t	attr;
pthread_mutex_t		mutex;

void retry(void *arg)
{
	pthread_mutex_lock(&mutex);
	/* 重试步骤 */
	pthread_mutex_unlock(&mutex);
}

int main(void)
{
    int             err, condition, arg;
    struct timespec when;

    condition = 1;
    arg = 1;

    // 用默认的互斥量属性初始化pthread_mutexattr结构
    if ((err = pthread_mutexattr_init(&attr)) != 0) {
        fprintf(stderr, "pthread_mutexattr_init failed: %s\n", strerror(err));
        exit(err);
    }   

    // 修改互斥量类型属性:运行同一线程在互斥量解锁之前对该互斥量进行多次加锁 
    if ((err = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) != 0) {
        fprintf(stderr, "can't set recursive type: %s\n", strerror(err));
        exit(err);
    }   

    // 初始化互斥量
    if ((err = pthread_mutex_init(&mutex, &attr)) != 0) {
        fprintf(stderr, "can't create recursive mutex: %s\n", strerror(err));
        exit(err);
    }   

    pthread_mutex_lock(&mutex);              // 对互斥量进行加锁
    /* ... */
    if (condition) {
        timeout(&when, retry, (void *)&arg);
    }   
    /* ... */
    pthread_mutex_unlock(&mutex);            // 对互斥量进行解锁
    /* ... */
    exit(0);
}

        如果不能创建线程,或者安排函数运行的时间已过,问题就出现了。在这种情况下,要从当前环境中调用之前请求运行的函数,因为函数要获取锁和现在占有的锁是同一个,除非锁是递归的,否则就会出现死锁。

        timeout的调用者需要占有互斥量来检查条件,并且把retry函数安排为原子操作。retry函数试图对同一个互斥量进行加锁,因此,除非互斥量是递归的,否则如果timeout函数直接调用retry就会导致死锁。

2. 读写锁属性

        用pthread_rwlockattr_init初始化pthread_rwlockattr_t结构,用pthread_rwlockattr_destroy回收结构。

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

        读写锁支持的唯一属性是进程共享属性,该属性与互斥量的进程共享属性相同。就像互斥量的进程共享属性一样,用一对函数来读取和设置读写锁的进程共享属性。

       #include <pthread.h>
       int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *restrict attr, int *restrict pshared);
       int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);
        // 两个函数的返回值:若成功则返回0,否则返回错误编号

        虽然POSIX只定义了一个读写锁属性,但不同平台的实现可以自由地定义额外的、非标准的属性。

3. 条件变量属性

        条件变量也有属性。与互斥量和读写锁类似,有一对函数用于初始化和回收条件变量属性。

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

        与其他的同步原语一样,条件变量支持进程共享属性。

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

你可能感兴趣的:(读书笔记,《UNIX环境高级编程》)