以Linux下的测试程序说明递归型互斥量和普通互斥量的区别

先贴代码和测试结果

// Mutex.h: 对pthread的互斥量的RAII包装
#ifndef _MUTEX_H_
#define _MUTEX_H_

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

// 使用错误码errnum和字符串msg来打印错误信息, 并且退出程序
static inline void errExitEN(int errnum, const char* msg)
{
    fprintf(stderr, "%s Error: %s\n", msg, strerror(errnum));
    exit(1);
}

class Mutex
{
public:
    explicit Mutex()
    {
        int s;
        pthread_mutexattr_t attr;
        s = pthread_mutexattr_init(&attr);
        if (s != 0)
            errExitEN(s, "pthread_mutexattr_init");
        s = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
#ifdef ERRORCHECK
        s = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
#elif RECURSIVE
        s = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
#endif
        if (s != 0)
            errExitEN(s, "pthread_mutexattr_settype");

        pthread_mutex_init(&__mtx, &attr);
        if (s != 0)
            errExitEN(s, "pthread_mutex_init");

        s = pthread_mutexattr_destroy(&attr);
        if (s != 0)
            errExitEN(s, "pthread_mutexattr_destroy");
    }

    virtual ~Mutex()
    {
        int s = pthread_mutex_destroy(&__mtx);
        if (s != 0)
            errExitEN(s, "pthread_mutex_destroy");
    }

    void lock()
    {
        int s = pthread_mutex_lock(&__mtx);
        if (s != 0)
            errExitEN(s, "pthread_mutex_lock");
    }

    void unlock()
    {
        int s = pthread_mutex_unlock(&__mtx);
        if (s != 0)
            errExitEN(s, "pthread_mutex_unlock");
    }
private:
    pthread_mutex_t __mtx;
};

#endif
// MutexTest.cpp: Mutex类对于重复获取同一把锁的测试
#include "Mutex.h"
#include <stdio.h>
#include <pthread.h>
#include <array>

Mutex mtx;
std::array<int, 10> g_array;

// 将g_array[index]左边的元素自增(使用互斥量来保护)
void incrLeftWithMutex(int index)
{
    mtx.lock();
    for (int i = 0; i < index; i++)
        g_array[i]++;
    mtx.unlock();
}

// 将g_array[index]右边的元素自增(使用互斥量来保护)
void incrRightWithMutex(int index)
{
    mtx.lock();
    for (int i = index + 1; i < (int) g_array.size(); i++)
        g_array[i]++;
    mtx.unlock();
}

// 将g_array[index]以外的元素自增
void incrOtherItem(int index)
{
    mtx.lock();
    incrLeftWithMutex(index);
    incrRightWithMutex(index);
    mtx.unlock();
}

int main()
{
    g_array.fill(0);
    incrOtherItem(5);

    for (int i : g_array)
        printf("%d ", i);
    printf("\n");
    return 0;
}
$ g++ MutexTest.cpp -std=c++11 -pthread
$ time ./a.out
^C
real    0m3.973s
user    0m0.004s
sys    0m0.000s

$ g++ MutexTest.cpp -std=c++11 -pthread -DERRORCHECK
$ ./a.out
pthread_mutex_lock Error: Resource deadlock avoided
pthread_mutex_destroy Error: Device or resource busy
$ g++ MutexTest.cpp -std=c++11 -pthread -DRECURSIVE
$ ./a.out
1 1 1 1 1 0 1 1 1 1 

不额外定义宏则使用默认的互斥量(锁),定义宏ERRORCHECK则锁自带错误检查,定义宏RECURSIVE则代表递归锁。

主线程中调用了incrOtherItem函数,该函数先获取(acquire)锁mtx,然后调用另外2个函数后释放(release)锁mtx。

实验结果显示默认锁陷入了死锁,错误检查的结果是resource deadlock avoided(即陷入了死锁),而递归锁则成功执行了下去。

因为向一把已经被获取的锁申请上锁时,线程会阻塞一直到已获取锁的一方将锁释放。所以若线程已经获取了锁A而未释放,当它再次获取锁A时会陷入死锁,因为此线程会阻塞直到锁A被释放,然后只有拥有锁的线程(也就是它自己)才能释放锁,而线程自己处于阻塞中,所以永远处于阻塞状态。

递归锁就是为了解决这种状况,从incrOtherItem的函数定义看起来代码没任何问题,但是incrLeftWithMutex和incrRightWithMutex函数试图获取了同一把锁,这样相当于未释放锁就再次获取同一把锁。

递归锁会在内部维护一个计数器,当线程第1次获取互斥量时,计数器置为1,之后该线程可以在此获取同一把锁,每次获取锁计数器加1,每次释放锁计数器减1。由于此时其他线程无法获取锁,所以只要保证该线程的执行过程是可重入的,代码就没问题。

由于这种情况往往是函数递归调用时才出现的,比如

函数1:上锁,调用函数2,解锁。

函数2:上锁……解锁

函数1的过程就变成了:上锁,函数1的内容(第一部分),上锁,函数2的内容,解锁,函数1的内容(第二部分),解锁。

如果函数1的内容是不可重入的,而函数2修改了函数1的操作对象,那么这里就会出问题。

比如。函数1是想获取全局int数组(设为int a[4] = { 1,2,3,4 } )的总和,第一部分是求前半部分的和,第二部分是求后半部分的和。

而函数2若导致int数组发生了变化,比如让a[2] = 0,这样最后求得的和就是1+2+0+4=7而不是1+2+3+4=10。

时间: 2024-08-27 09:41:54

以Linux下的测试程序说明递归型互斥量和普通互斥量的区别的相关文章

Linux下文件的三个时间:ctime、mtime、atime的区别

Linux下,文件的三个时间参数 (1)modification time(mtime):内容修改时间 这里的修改时间指的是文件的内容发生变化,而更新的时间. Eg:vi后保存文件. (2)status time(ctime):状态修改时间 这里的修改时间指的是文件的属性或者权限发生变化,而更新的时间. Eg:通过chmod.chown命令修改一次文件属性,这个时间就会更新. (3)access time(atime):最后访问时间 这里的访问时间是指文件被读取,而更新的时间. Eg:对这个文件

linux下c语言实现递归复制文件夹

#include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <unistd.h> #include <dirent.h> #include <string.h> #include <errno.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/type

Linux下查找文件:which、whereis、locate、find 命令的区别

我们经常在linux要查找某个文件,但不知道放在哪里了,可以使用下面的一些命令来搜索.which       查看可执行文件的位置,通过环境变量查whereis    查看文件的位置,通过数据库查,每周更新locate       配合数据库查看文件位置,通过数据库查,每周更新find          实际搜寻硬盘查询文件名称,查硬盘 1.which 语法: [[email protected] ~]# which 可执行文件名称 例如: [[email protected] ~]# whic

Linux 下用vfork()创建进程,子进程用return和exit返回的区别

1 #include <stdio.h> 2 #include <unistd.h> 3 #include <sys/types.h> 4 #include <error.h> 5 6 int main() 7 { 8     pid_t id=vfork(); 9     if(id==29){//child 10         printf("child:hello world\n"); 11         //exit(0);

linux下多线程编程

最近研究mysql源码,各种锁,各种互斥,好在我去年认真学了<unix环境高级编程>, 虽然已经忘得差不多了,但是学过始终是学过,拿起来也快.写这篇文章的目的就是总结linux 下多线程编程,作为日后的参考资料. 本文将介绍linux系统下多线程编程中,线程同步的各种方法.包括: 互斥量(mutex) 读写锁 条件变量 信号量 文件互斥 在介绍不同的线程同步的方法之前,先简单的介绍一下进程和线程的概念, 它们的优缺点,线程相关的API,读者——写者问题和哲学家就餐问题. 基础知识 1. 进程和

linux下递归删除空目录的bash实例

# $1必须是绝对路径crurl=$1func_hdir(){echo $crurl  cd $crurl  for aitem in `ls -l | grep "^d" | awk '{print $9}'`; do        crurl=$crurl/$aitem        func_hdir $aitem  done dirc=`ls $crurl`  if [ "$dirc" = "" ]  then    echo $crur

Perf Event :Linux下的系统性能调优工具

Perf Event :Linux下的系统性能调优工具 2011-05-27 10:35 刘 明 IBMDW 字号:T | T Perf Event 是一款随 Linux 内核代码一同发布和维护的性能诊断工具,由内核社区维护和发展.Perf 不仅可以用于应用程序的性能统计分析,也可以应用于内核代码的性能统计和分析.得益于其优秀的体系结构设计,越来越多的新功能被加入 Perf,使其已经成为一个多功能的性能统计工具集 .本文将介绍 Perf 在应用程序开发上的应用. AD:2014WOT全球软件技术

Linux下的NTP

一.电脑时间的误差众 所周知,电脑主机的时间是根据电脑晶振以固定频率振荡,从而产生的.由于晶振的不同,会导致电脑时间与UTC时间 (全球标准时间:全球标准时间指的是由世界时间标准设定的时间.原先也被称为格林威治标准时间或者 GMT) 总会存在差异.所以,为了避免电脑时间长期积累下导致的时间偏差越来越大,就必须定期进行对电脑时间进行设置调整.用户可以通过手表.时钟或者是电视时间 来进行时间校准,对于电脑用户来说,最方便的是通过Internet上的时间服务器校准电脑时间,我们称之为时间同步. 二.时

linux下gcc编译多个源文件、gdb的使用方法(转)

原文 http://www.cnblogs.com/jiu0821/p/4483804.html 一. gcc常用编译命令选项 假设源程序文件名为test.c. 1. 无选项编译链接 用法:#gcc test.c 作用:将test.c预处理.汇编.编译并链接形成可执行文件.这里未指定输出文件,默认输出为a.out. 2. 选项 -o 用法:#gcc test.c -o test 作用:将test.c预处理.汇编.编译并链接形成可执行文件test.-o选项用来指定输出文件的文件名. 3. 选项 -