92-递归型互斥量

互斥量的类型属性通常有四种:

  • PTHREAD_MUTEX_NORMAL
  • PTHREAD_MUTEX_ERRORCHECK
  • PTHREAD_MUTEX_RECURSIVE
  • PTHREAD_MUTEX_DEFAULT

其中第一种和第四种一般都是一样的,宏定义的值相同,是默认情况。第二种提供错误检查。第三种是我们本文需要讨论的。

1. 相关函数

可以使用下面的函数对互斥量的类型属性进行设置和获取:

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

int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);

2. 递归类型的互斥量

一般情况下,我们在同一个线程中对同一个互斥量加两次锁,就会死锁。如果将互斥量类型属性设置为递归类型 PTHREAD_MUTEX_RECURSIVE 就不会出现此问题。

递归互斥量内部维护着一个计数器,当互斥量未上锁时,计数器值为 0。只有计数器为 0 的情况下,线程才能够获得锁。只有获得锁的线程,才能持续对互斥量加锁,每加一次锁,计数器的值加 1,每解一次锁,计数器的值减 1.

3. 实验

程序 recsig 的功能是在信号处理函数内部对余票数量加 1. 操作余票的时候,需要加锁。recsig 程序可以从命令行接受一个参数,表示使用递归互斥量,如果不传参数,表示使用普通互斥量。

resig 函数注册了两种信号,第一种是 SIGUSR1,另一种是 SIGINT。当程序启动的时候,会自己给自己发送 SIGUSR1 信号。

3.1 代码

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <pthread.h>

#define PPERR(err, msg) do { errno = err; perror(msg); exit(-1); } while(0)

struct ticket {
  int remain;
  pthread_mutex_t lock;
};

struct ticket t;

void printtype(pthread_mutexattr_t *attr) {
  int err, type;
  char *s = "???";
  err = pthread_mutexattr_gettype(attr, &type);
  if (err != 0) PPERR(err, "pthread_mutexattr_gettype");

  if (type == PTHREAD_MUTEX_NORMAL) {
      s = "PTHREAD_MUTEX_NORMAL";
      printf("MUTEX TYPE = %s\n", s); 
  }
  if (type == PTHREAD_MUTEX_ERRORCHECK) {
      s = "PTHREAD_MUTEX_ERRORCHECK";
      printf("MUTEX TYPE = %s\n", s); 
  }
  if (type == PTHREAD_MUTEX_RECURSIVE) {
      s = "PTHREAD_MUTEX_RECURSIVE";
      printf("MUTEX TYPE = %s\n", s); 
  }
  if (type == PTHREAD_MUTEX_DEFAULT) {
      s = "PTHREAD_MUTEX_DEFAULT";
      printf("MUTEX TYPE = %s\n", s); 
  }
}


void setrecursive(pthread_mutexattr_t *attr) {
  int err, type;
  type = PTHREAD_MUTEX_RECURSIVE;
  err = pthread_mutexattr_settype(attr, type);
  if (err != 0) PPERR(err, "pthread_mutexattr_settype");
}

void handler(int sig) {
  if (sig == SIGUSR1) puts("receive SIGUSR1");
  else if (sig == SIGINT) puts("receive SIGINT");
  pthread_mutex_lock(&t.lock);
  printf("%s enter handler\n", sig == SIGUSR1 ? "SIGUSR1":"SIGINT");
  t.remain++;
  sleep(3);
  printf("%s exit handler\n", sig == SIGUSR1 ? "SIGUSR1":"SIGINT");
  pthread_mutex_unlock(&t.lock);
}

int main(int argc, char* argv[]) {
  int recursive = 0;
  if (argc >= 2) recursive = 1;

  t.remain = 3;

  pthread_mutexattr_t attr;
  pthread_mutexattr_init(&attr);
  printtype(&attr);
  if (recursive == 1) {
    puts("modify type --------------------->");
    setrecursive(&attr);
    printtype(&attr);
  }
  pthread_mutex_init(&t.lock, &attr);
  pthread_mutexattr_destroy(&attr);

  signal(SIGUSR1, handler);
  signal(SIGINT, handler);

  puts("send SIGUSR1");
  kill(getpid(), SIGUSR1);

  pthread_mutex_destroy(&t.lock);

  printf("remain = %d\n", t.remain);

  return 0;
}

3.2 编译和运行

  • 编译
$ gcc recsig.c -o recsig -lpthread
  • 不带参数运行
$ ./recsig

当进入信号处理函数的时候,按下 CTRL+ C,接下来,程序死锁。原因在于信号处理函数在执行的时候,收到了 SIGINT 信号,又加了一次锁,注意它们都属于同一个线程,所以导致了死锁。


这里写图片描述
图1 普通类型互斥量运行结果

  • 带参数运行
$ ./recsig 1

当进入信号处理函数的时候,按下 CTRL+ C,程序正常执行完成。


这里写图片描述
图2 递归类型互斥量运行结果

4. 总结

  • 理解递归类型互斥量的作用
  • 信号处理函数中,避免使用互斥量,如果确实要使用,使用递归类型互斥量
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值