目录
互斥锁相信大家都知道其作用了,就是用来排他性的访问共享资源,那也就是说哪个线程抢到了锁那这个线程就可以独占这个共享资源,其他线程只能排队等待了。那互斥锁的用法其实也不复杂,在需要独占访问的资源地方加锁即可。
一,锁的类型
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,这个不得而知,没找到相应的源码,有知道的同学可以告知一下。