pthread_mutex 互斥锁

       

目录

一,锁的类型

二,锁的数据结构

三、来个例子

1,普通的锁

2,递归锁


        互斥锁相信大家都知道其作用了,就是用来排他性的访问共享资源,那也就是说哪个线程抢到了锁那这个线程就可以独占这个共享资源,其他线程只能排队等待了。那互斥锁的用法其实也不复杂,在需要独占访问的资源地方加锁即可。

一,锁的类型

enum
{
  PTHREAD_MUTEX_TIMED_NP,
  PTHREAD_MUTEX_RECURSIVE_NP,
  PTHREAD_MUTEX_ERRORCHECK_NP,
  PTHREAD_MUTEX_ADAPTIVE_NP
#if defined __USE_UNIX98 || defined __USE_XOPEN2K8
  ,
  PTHREAD_MUTEX_NORMAL = PTHREAD_MUTEX_TIMED_NP,
  PTHREAD_MUTEX_RECURSIVE = PTHREAD_MUTEX_RECURSIVE_NP,
  PTHREAD_MUTEX_ERRORCHECK = PTHREAD_MUTEX_ERRORCHECK_NP,
  PTHREAD_MUTEX_DEFAULT = PTHREAD_MUTEX_NORMAL
#endif
#ifdef __USE_GNU
  /* For compatibility.  */
  , PTHREAD_MUTEX_FAST_NP = PTHREAD_MUTEX_TIMED_NP
#endif
};

常用的第一种,普通的锁;第二种,递归锁。其他没遇到过,这里就不讲了。那它俩有啥区别呢?

二,锁的数据结构

/* Data structures for mutex handling.  The structure of the attribute
   type is not exposed on purpose.  */
typedef union
{
  struct __pthread_mutex_s
  {
    int __lock;
    unsigned int __count;
    int __owner;
#ifdef __x86_64__
    unsigned int __nusers;
#endif
    /* KIND must stay at this position in the structure to maintain
       binary compatibility.  */
    int __kind;
#ifdef __x86_64__
    short __spins;
    short __elision;
    __pthread_list_t __list;
# define __PTHREAD_MUTEX_HAVE_PREV	1
/* Mutex __spins initializer used by PTHREAD_MUTEX_INITIALIZER.  */
# define __PTHREAD_SPINS             0, 0
#else
    unsigned int __nusers;
    __extension__ union
    {
      struct
      {
	short __espins;
	short __elision;
# define __spins __elision_data.__espins
# define __elision __elision_data.__elision
# define __PTHREAD_SPINS         { 0, 0 }
      } __elision_data;
      __pthread_slist_t __list;
    };
#endif
  } __data;
  char __size[__SIZEOF_PTHREAD_MUTEX_T];
  long int __align;
} pthread_mutex_t;

它的数据结构是一个联合体,我们看__data 即可,实际它使用的也是这个域。几个字段比较关键:

1,__lock 表示是否已经锁定,即有线程调用了 pthread_mutex_lock() 后,这个字段的值为 1;

2,__count 表示当前锁的被加的次数,这个用在递归锁里;

3,__owner 表示锁的所有者的线程id,即哪个线程调用了 pthread_mutex_lock,这个值很关键,因为在排查死锁的时候就是根据这个 __owner 来一步步排查哪个线程获取了锁,哪个线程在等待锁,可以参考 这里

三、来个例子

1,普通的锁

即锁的属性是默认的

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/syscall.h>

pthread_mutex_t mut;

void *threadPro(void *arg)
{
    printf("child thread id = %d, wait to get mutex....\n", syscall(SYS_gettid));
    int ret = pthread_mutex_lock(&mut);
    printf("in child thread, ret = %d, lock = %d, count = %d, owner = %d\n", ret, mut.__data.__lock, mut.__data.__count, mut.__data.__owner);
}

int main()
{
    printf("main thread id = %d\n", syscall(SYS_gettid));

    pthread_t tid;
    pthread_mutex_init(&mut, NULL);

    pthread_create(&tid, NULL, threadPro, NULL);

    int ret = pthread_mutex_lock(&mut);
    printf("1, ret = %d, lock = %d, count = %d, owner = %d\n", ret, mut.__data.__lock, mut.__data.__count, mut.__data.__owner);

    pthread_mutex_unlock(&mut);
    printf("2, ret = %d, lock = %d, count = %d, owner = %d\n", ret, mut.__data.__lock, mut.__data.__count, mut.__data.__owner);
    
    pthread_join(tid, NULL);

    pthread_mutex_destroy(&mut);

    return 0;
}

可以看到在主线程里调用 pthread_mutex_lock() 后,__data.__lock = 1, __data.__count = 0, __data.__owner = 主线程id;在解锁后,这些值都置为0。接着在子线程里获取到了锁,__data.__lock = 1, __data.__owner = 子线程id。

2,递归锁

设置锁的属性的递归类型:PTHREAD_MUTEX_RECURSIVE_NP。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/syscall.h>

pthread_mutex_t mut;
pthread_mutexattr_t attr;

void *threadPro(void *arg)
{
    printf("child thread id = %d, wait to get mutex....\n", syscall(SYS_gettid));
    int ret = pthread_mutex_lock(&mut);
    printf("in child thread, ret = %d, lock = %d, count = %d, owner = %d\n", ret, mut.__data.__lock, mut.__data.__count, mut.__data.__owner);
    pthread_mutex_unlock(&mut);
}

int main()
{
    printf("main thread id = %d\n", syscall(SYS_gettid));

    pthread_t tid;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP);
    pthread_mutex_init(&mut, &attr);

    pthread_create(&tid, NULL, threadPro, NULL);

    int ret = pthread_mutex_lock(&mut);
    printf("1, ret = %d, lock = %d, count = %d, owner = %d\n", ret, mut.__data.__lock, mut.__data.__count, mut.__data.__owner);

    ret = pthread_mutex_lock(&mut);
    printf("2, ret = %d, lock = %d, count = %d, owner = %d\n", ret, mut.__data.__lock, mut.__data.__count, mut.__data.__owner);

    pthread_mutex_unlock(&mut);
    printf("3, ret = %d, lock = %d, count = %d, owner = %d\n", ret, mut.__data.__lock, mut.__data.__count, mut.__data.__owner);
    
    pthread_join(tid, NULL);
    

    pthread_mutex_destroy(&mut);
    return 0;
}

这个代码执行会有什么问题吗?其结果是程序一直阻塞,因为子线程根本获取不到锁,而主线程又一直在等待子线程结束。

那为什么子线程获取不到锁呢? 就是因为 __data.__count 这个值,这个值在递归锁类型中会使用。我们看源码:

源码中的处理就是如果是递归锁,同一个线程调用 pthread_mutex_lock() 则 __data.__count 累加,然后就返回。如果不是同一个线程,如上面的例子中子线程调用 pthread_mutex_lock(),则调用了 LLL_MUTEX_LOCK_OPTIMIZED(mutex) 这是一个宏:

到这里 lll_lock() 可能是阻塞调用了,没找到这个函数定义,这里也就不纠结了。那子线程要怎样才能获取到锁呢?那当然就是加了几次锁,相应的解几次锁了,这个操作只能是同一个线程操作,如在子线程中解锁会是怎样的结果?

void *threadPro(void *arg)
{
    printf("child thread id = %d, wait to get mutex....\n", syscall(SYS_gettid));
    int ret = pthread_mutex_unlock(&mut);
    printf("in child thread, ret = %d, errmsg: %s, lock = %d, count = %d, owner = %d\n", ret, strerror(ret),mut.__data.__lock, mut.__data.__count, mut.__data.__owner);
    pthread_mutex_lock(&mut);
    printf("in child thread, do something\n");

    pthread_mutex_unlock(&mut);
}

看 man 手册返回的应该是 EPERM 这个值,但这个并没有设置errno

很明显子线程并未拥有锁,所以并不能解锁。 然后改成下面这样,在子线程中就能成功获取到锁了:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/syscall.h>

pthread_mutex_t mut;
pthread_mutexattr_t attr;

void *threadPro(void *arg)
{
    printf("child thread id = %d, wait to get mutex....\n", syscall(SYS_gettid));
    int ret = pthread_mutex_lock(&mut);
    printf("in child thread, ret = %d, lock = %d, count = %d, owner = %d\n", ret, mut.__data.__lock, mut.__data.__count, mut.__data.__owner);
}

int main()
{
    printf("main thread id = %d\n", syscall(SYS_gettid));

    pthread_t tid;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP);
    pthread_mutex_init(&mut, &attr);

    pthread_create(&tid, NULL, threadPro, NULL);

    int ret = pthread_mutex_lock(&mut);
    printf("1, ret = %d, lock = %d, count = %d, owner = %d\n", ret, mut.__data.__lock, mut.__data.__count, mut.__data.__owner);

    ret = pthread_mutex_lock(&mut);
    printf("2, ret = %d, lock = %d, count = %d, owner = %d\n", ret, mut.__data.__lock, mut.__data.__count, mut.__data.__owner);

    pthread_mutex_unlock(&mut);
    printf("3, ret = %d, lock = %d, count = %d, owner = %d\n", ret, mut.__data.__lock, mut.__data.__count, mut.__data.__owner);
    
    sleep(5);
    pthread_mutex_unlock(&mut);
    pthread_join(tid, NULL);
    
    pthread_mutex_destroy(&mut);
    return 0;
}

在 main 函数中,调用了 2 次 pthread_mutex_lock,相应的也要调用 2 次 pthread_mutex_unlock。

主线程在休眠 5 秒后解锁,相应的子线程就成功获取到了锁。但这个 __data.__lock 为何是 2,这个不得而知,没找到相应的源码,有知道的同学可以告知一下。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值