线程控制(二)

本文详细探讨了Unix/Linux环境下的线程控制,包括线程限制、互斥量属性(如递归互斥量)、读写锁属性、条件变量属性、屏蔽属性、重入、线程特定数据、取消选项、线程和信号的交互、线程与fork的关系以及线程在I/O操作中的应用。重点讨论了递归互斥量在多线程环境中的使用场景和问题,以及如何避免使用递归互斥量导致的死锁。同时介绍了线程安全函数、线程特定数据和线程取消选项,帮助理解和优化多线程程序设计。
摘要由CSDN通过智能技术生成

一、线程限制

线程限制:

在这里插入图片描述
在这里插入图片描述

二、互斥量属性

#include <pthread.h>
//查询pthread_mutexattr_t结构,得到它的进程共享属性
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared); 
//修改进程共享属性。
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared); 
//两个函数的返回值:若成功,返回0;否则,返回错误编号
#include <pthread.h> 

//获取健壮的互斥量属性的值
int pthread_mutexattr_getrobust(const pthread_mutexattr_t *restrict attr, int *restrict robust);

//设置健壮的互斥量属性的值。
int pthread_mutexattr_setrobust(pthread_mutexattr_t *attr, int robust); 
//两个函数的返回值:若成功,返回0;否则,返回错误编号

健壮属性取值有两种可能的情况:

  1. 默认值 PTHREAD_MUTEX_STALLED,这意味着持有互斥量的进程终止时不需要采取特别的动作。这种情况使用互斥量是未定义的,等待该互斥量解锁的应用程序会被有效的“拖住”。

  2. PTHREAD_MUTEX_ROBUST导致线程调用pthread_mtex_lock获取所,该锁被另一个进程持有,终止是没有对该锁进行解锁,此时线程会阻塞,从pthread_mtex_lock返回值为EOWNERDEAD而不是0。有可能保护的互斥量都需要进行恢复。

使用健壮的互斥量改变了我们使用pthread_mutex_lock的方式,因为现在必 须检查3个返回值而不是之前的两个:不需要恢复的成功、需要恢复的成功以及 失败。即使不用健壮的互斥量,也可以只检查成功或者失败。

//指明互斥量的状态在互斥量解锁之前是一致的。
#include <pthread.h> int pthread_mutex_consistent(pthread_mutex_t *mutex); 
//线程未调用此函数就对互斥量解锁,其他获取该互斥量的阻塞线程就会得到错误码ENOTRECOVERABLE,这时互斥量将不再可用。

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

类型互斥量属性控制着互斥量的锁定特性,POSIX.1定义了4种类型:

在这里插入图片描述

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

1.递归互斥量

PTHREAD_MUTEX_RECURSIVE(递归互斥量)允许同一线程在互斥量解 锁之前对该互斥量进行多次加锁。递归互斥量维护锁的计数,在解锁次数和加 锁次数不相同的情况下,不会释放锁。所以,如果对一个递归互斥量加锁两 次,然后解锁一次,那么这个互斥量将依然处于加锁状态,对它再次解锁以前 不能释放该锁。

  • 互斥量用于保护与条件变量关联的条件,在阻塞线程之前,pthread_cond_waitpthread_cond_timewait函数释放与条件相关的互斥量,其他线程就能获取互斥量、改变条件、释放互斥量以及给条件变量 发信号。

    • 改变条件时必须占有互斥量,使用递归互斥量就不是一个好主意。如果递归互斥量被多次加锁,然后用在调用pthread_cond_wait函数中,那么条件永远都不会得到满足,因为pthread_cond_wait所做的解锁操作并不能释放互 斥量。

      • 把现有的单线程接口放到多线程环境中,递归互斥量是非常有用的,但由于现有程序兼容性的限制,不能对函数接口进行修改。这时使用递归锁可能很难处理。

下图使用递归互斥量好像是在解决并发问题。若func1和func2是函数库中现有的函数,接口不能改变,因为存在调用这两个接口的应用程序,而且应用程序不能改动。

在这里插入图片描述

  • 为了保持接口跟原来相同,我们把互斥量嵌入到了数据结构中,把这个**数 据结构的地址(x)**作为参数传入。这种方案只有在为此数据结构提供分配函数 时才可行,所以应用程序并不知道数据结构的大小(假设我们在其中增加互斥 量之后必须扩大该数据结构的大小)。

    • 如果在最早定义数据结构时,预留了足够的可填充字段,允许把某些填充 字段替换成互斥量,这种方法也是可行的。
  1. 如果func1和func2函数都必须操作这个结构,而且可能会有一个以上的线程 同时访问该数据结构,那么 func1 和 func2 必须在操作数据以前对互斥量加锁。
  2. 如果 func1 必须调用func2,这时如果互斥量不是递归类型的,那么就会出现死 锁
  3. 若能在调用 func2 之前释放互斥量,在 func2 返回后重新获取互斥量,那 么就可以避免使用递归互斥量,但其他的线程 可以在 func1 执行期间抓住互斥量的控制,修改这个数据结构。这也许是不可 接受的,当然具体的情况要取决于互斥量试图提供什么样的保护。

下图显示这种情况下使用递归互斥量的一种替代方法:

在这里插入图片描述

  1. 通过提供func2 函数的私有版本,称之为func2_locked函数,可以保持func1和func2函数接口不 变,而且避免使用递归互斥量。

  2. 要调用 func2_locked 函数,必须占有嵌入在数 据结构中的互斥量,这个数据结构的地址是作为参数传入的。

  3. func2_locked的函 数体包含func2的副本,func2现在只是获取互斥量,调用func2_locked,然后释放互斥量。

  • 如果并不一定要保持库函数接口不变,就可以在每个函数中增加第二个参 数表明这个结构是否被调用者锁定。但是保持接口不变通常是更好的选择,可以避免实现过程中人为加入的东西对原有系统产生不良影 响。

    • 提供加锁和不加锁版本的函数,这样的策略在简单的情况下通常是可行 的。在更加复杂的情况下,比如,库需要调用库以外的函数,而且可能会再次 回调库中的函数时,就需要依赖递归锁。

例:例子很简单,在main函数里创建2个线程,在线程1的函数fn1,加锁互斥量2次,但是只解锁一次。线程fn2就无法给互斥量加锁,导致一直阻塞在a处。为了能够让线程fn1能够先给互斥量加锁,在fn2里调用了sleep函数,让fn2先睡眠1秒,所以fn1就能够先给互斥量加锁了。去掉b处的注释,fn2就能锁定mutex了,程序就不会出现死锁状态了。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <signal.h>
 
pthread_mutex_t mt;
int i = 0;
 
void* fn1(void* agr)
{
   
  int err;
 
  pthread_mutex_lock(&mt);
  if((err = pthread_mutex_lock(&mt)) < 0)
  {
   
    printf("%s\n", strerror(err));
    exit(1);
  }
 
  ++i;
  printf("%d\n", i);
 
  //pthread_mutex_unlock(&mt);//b
  pthread_mutex_unlock(&mt);
}
 
void* fn2(void* arg)
{
   
  sleep(1);//目的是让线程fn1先执行。
  pthread_mutex_lock(&mt);//a
  ++i;
  printf("second %d\n", i);
  pthread_mutex_unlock(&mt);
}
 
int main()
{
   
  pthread_t tid1, tid2;
 
  pthread_mutexattr_t mat;
  pthread_mutexattr_init(&mat);
 
  //设置锁的类型为递归锁
  pthread_mutexattr_settype(&mat, PTHREAD_MUTEX_RECURSIVE);
  pthread_mutex_init(&mt, &mat);
    
  pthread_create(&tid1, NULL, fn1, NULL);
  pthread_create(&tid2, NULL, fn2, NULL);
 
  pthread_join(tid1, NULL);
  pthread_join(tid2, NULL);
 
  pthread_mutex_destroy(&mt);
}

C++11替换的递归互斥量 std::recursive_mutex

#include <iostream>
#include <thread>
#include <mutex>
#include <unistd.h>
 
int g_resourceID = 1;
std::recursive_mutex mtx;
 
void ThreadFunc_2() {
   
    mtx.lock();                     //第二次上锁
 
	g_resourceID = g_resourceID * g_resourceID ;
	printf("g_resourceID = %d \n", g_resourceID );
	mtx.unlock();
}
 
void ThreadFunc_1() {
   
	mtx.lock(); 
 
	g_resourceID = g_resourceID + 1;
	ThreadFunc_2();
	printf("g_resourceID 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值