APUE——线程同步属性与递归锁

参考文档1
参考文档2
递归锁

1、互斥量属性

值得注意的两个属性是进程共享属性类型属性

#include <pthread.h>

int pthread_mutexattr_init( pthread_mutexattr_t *attr );

int pthread_mutexattr_destroy( pthread_mutexattr_t *attr );
int pthread_mutexattr_getpshared(pthread_mutexattr_t *attr,int *pshared);  
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr,int pshared);
返回值:若成功则返回0,否则返回错误编号

pshared的取值

  1. THREAD_PROCESS_SHARED:那么由这个属性对象创建的互斥锁将被保存在共享内存中,可以被多个进程中的线程共享。
  2. PTHREAD_PROCESS_PRIVATE:那么只有和创建这个互斥锁的线程在同一个进程中的线程才能访问这个互斥锁。
int pthread_mutexattr_settype(pthread_mutexattr_t *attr,int kind);  
int pthread_mutexattr_gettype(pthread_mutexattr_t *attr,int *kind);

  1. PTHREAD_MUTEX_NORMAL,不进行deadlock detection(死锁检测)。企图进行relock这个mutex会导致deadlock.如果一个线程对未加锁的或已经unlock的mutex对象进行unlock操作,结果是不未知的。
  2. PTHREAD_MUTEX_ERRORCHECK,那么将进行错误检查。如果一个线程企图对一个已经锁住的mutex进行relock,将返回一个错误。如果一个线程对未加锁的或已经unlock的mutex对象进行unlock操作,将返回一个错误。
  3. PTHREAD_MUTEX_RECURSIVE,mutex会有一个锁住次数(lock count)的概念。当一个线程成功地第一次锁住一个mutex的时候,锁住次数(lock count)被设置为1,每一次一个线程unlock这个mutex的时候,锁住次数(lock count)就减1。当锁住次数(lock count)减少为0的时候,其他线程就能获得该mutex锁了。如果一个线程对未加锁的或已经unlock的mutex对象进行unlock操作,将返回一个错误,如果一个线程对这种类型的互斥锁重复上锁,不会引起死锁,一个线程对这类互斥锁的多次重复上锁必须由这个线程来重复相同数量的解锁,这样才能解开这个互斥锁,别的线程才能得到这个互斥锁。如果试图解锁一个由别的进程锁定的互斥锁将会返回一个错误代码。如果一个线程试图解锁已经被解锁的互斥锁也将会返回一个错误代码。这种类型的互斥锁只能是进程私有的(作用域属性为PTHREAD_PROCESS_PRIVATE)。
  4. PTHREAD_MUTEX_DEFAULT,企图递归的获取这个mutex的锁的结果是不确定的。unlock一个不是被调用线程锁住的mutex的结果也是不确定的。企图unlock一个未被锁住的mutex导致不确定的结果。
    在这里插入图片描述

2、递归锁

Linux下的pthread_mutex_t锁默认是非递归的
根据上文分析,在同一个线程中,如果想要多次获得一个锁,只能使用递归锁PTHREAD_MUTEX_RECURSIVE,不然会出现死锁,其次,递归锁是不被提倡的,用到递归锁说明这个代码设计是有问题的。更好的做法是,提取出一个被两个个公有函数调用的私有函数,这个私有函数无需锁定mutex

2.1 非递归锁出现死锁的情况

main()
{
    func1(x);
    ......
    func2(x);
}

func1()
{
    pthread_mutex_lock(x->lock);
    ......
    func2(x);
    ......
    pthread_mutex_unlock(x->lock);
}

func2()
{
    pthread_mutex_lock(x->lock);
    ......
    pthread_mutex_unlock(x->lock);
}

2.2 避免使用递归锁

两种办法:

  1. 将原来的加锁的函数分为不加锁的部分
  2. 作为对外接口的public函数只能调用无锁的私有变量函数,而不能互相调用
main()
{
    func1(x);
    ......
    func2(x);
}

func1()
{
    pthread_mutex_lock(x->lock);
    ......
    func2_locked(x);  //这里调用func2的非锁函数func2_locked(x),可以避免出现递归
    ......
    pthread_mutex_unlock(x->lock);
}

func2()
{
    pthread_mutex_lock(x->lock);
    func2_locked(x);
    pthread_mutex_unlock(x->lock);
}
//111111原来的版本
MutexLock mutex;

void foo()
{
	mutex.lock();
	// do something
	mutex.unlock();
}

void bar()
{
	mutex.lock();
	// do something
	foo();
	mutex.unlock();	
}
// 222222222不加锁版本
void foo_nolock()
{
	// do something
}
// 加锁版本
void fun()
{
    mutex.lock();
	foo_nolock();
	mutex.unlock();
}

// 33333333private版本
class T
{
public:
	foo(); //加锁
	bar(); //加锁
private:
	foo_nolock();
	bar_nolock();
}

作为对外接口的public函数只能调用无锁的私有变量函数,而不能互相调用。在函数具体实现上,这两种方法基本是一样的。

2.3 读写锁递归

  1. 先读在写,阻塞
  2. 先写在读,死锁
  3. 先读后读,正常
  4. 先写在写,死锁
    在POSIX标准中,如果一个线程先获得写锁,又获得读锁,则结果是无法预测的。这就是为什么程序1的运行出人所料。需要注意的是,读锁是递归锁(即可重入),写锁是非递归锁(即不可重入)。因此程序3不会死锁,而程序4会一直阻塞。
#include <pthread.h>
int main()
{
    pthread_rwlock_t rwl;
    pthread_rwlock_rdlock(&rwl);
    pthread_rwlock_wrlock(&rwl);
    pthread_rwlock_unlock(&rwl);
    pthread_rwlock_unlock(&rwl);
	return -1;
}

/*程序2*/
#include <pthread.h>
int main()
{
    pthread_rwlock_t rwl;
    pthread_rwlock_wrlock(&rwl);
    pthread_rwlock_rdlock(&rwl);
    pthread_rwlock_unlock(&rwl);
    pthread_rwlock_unlock(&rwl);
	return -1;
}

/*程序3*/
#include <pthread.h>
int main()
{
    pthread_rwlock_t rwl;
    pthread_rwlock_rdlock(&rwl);
    pthread_rwlock_rdlock(&rwl);
    pthread_rwlock_unlock(&rwl);
    pthread_rwlock_unlock(&rwl);
	return -1;
}
/*程序4*/
#include <pthread.h>
int main()
{
    pthread_rwlock_t rwl;
    pthread_rwlock_wrlock(&rwl);
    pthread_rwlock_wrlock(&rwl);
    pthread_rwlock_unlock(&rwl);
    pthread_rwlock_unlock(&rwl);
	return -1;
}

3. 其他同步属性

3.1 读写锁属性

读写锁与互斥量类似,也具有属性。用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>

#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,否则返回错误编号

3.2 条件变量属性

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

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

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

扫描二维码关注公众号,回复: 11273616 查看本文章
#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,否则返回错误编号

3.3 屏障属性

屏障也具有属性。我们可以使用pthread_barrierattr_init函数初始化一个屏障属性,使用pthread_barrierattr_destroy函数回收屏障属性。

#include <pthread.h>
int pthread_barrierattr_init(pthread_barrierattr_t *attr);
int pthread_barrierattr_destroy(pthread_barrierattr_t attr);
两个函数的返回值都是:若成功则返回0,否则返回错误编号

唯一的一个屏障属性是进程共享属性。

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

猜你喜欢

转载自blog.csdn.net/weixin_44537992/article/details/106103677