在多线程编程中,引入了对象互斥锁的概念,来保证共享数据操做的完整性。 每一个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻, 只能有一个线程访问该对象。linux
互斥锁也能够叫线程锁,接下来讲说互斥锁的的使用方法。编程
对互斥锁进行操做的函数,经常使用的有以下几个:安全
#include <pthread.h> int pthread_mutex_destroy(pthread_mutex_t *mutex); int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abs_timeout);
对线程锁进行操做的函数有不少,还包括许多线程锁属性的操做函数, 不过通常来讲,对于并不复杂的状况, 只须要使用建立、获取锁、释放锁、删除锁这几个就足够了。多线程
因此下面简单看一下如何建立和使用互斥锁。函数
在使用互斥锁以前,须要先建立一个互斥锁的对象。 互斥锁的类型是 pthread_mutex_t ,因此定义一个变量就是建立了一个互斥锁。测试
pthread_mutex_t mtx;
这就定义了一个互斥锁。可是若是想使用这个互斥锁仍是不行的,咱们还须要对这个互斥锁进行初始化, 使用函数 pthread_mutex_init() 对互斥锁进行初始化操做。ui
//第二个参数是 NULL 的话,互斥锁的属性会设置为默认属性 pthread_mutex_init(&mtx, NULL);
除了使用 pthread_mutex_init() 初始化一个互斥锁,咱们还可使用下面的方式定义一个互斥锁:this
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
在头文件 /usr/include/pthread.h 中,对 PTHREAD_MUTEX_INITIALIZER 的声明以下spa
# define PTHREAD_MUTEX_INITIALIZER \ { { 0, 0, 0, 0, 0, 0, { 0, 0 } } }
为何能够这样初始化呢,由于互斥锁的类型 pthread_mutex_t 是一个联合体, 其声明在文件 /usr/include/bits/pthreadtypes.h 中,代码以下:线程
/* 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; #if __WORDSIZE == 64 unsigned int __nusers; #endif /* KIND must stay at this position in the structure to maintain binary compatibility. */ int __kind; #if __WORDSIZE == 64 int __spins; __pthread_list_t __list; # define __PTHREAD_MUTEX_HAVE_PREV 1 #else unsigned int __nusers; __extension__ union { int __spins; __pthread_slist_t __list; }; #endif } __data; char __size[__SIZEOF_PTHREAD_MUTEX_T]; long int __align; } pthread_mutex_t;
接下来是如何使用互斥锁进行互斥操做。在进行互斥操做的时候, 应该先"拿到锁"再执行须要互斥的操做,不然可能会致使多个线程都须要访问的数据结果不一致。 例如在一个线程在试图修改一个变量的时候,另外一个线程也试图去修改这个变量, 那就极可能让后修改的这个线程把前面线程所作的修改覆盖了。
下面是获取锁的操做:
pthread_mutex_lock(&mtx);
这个操做是阻塞调用的,也就是说,若是这个锁此时正在被其它线程占用, 那么 pthread_mutex_lock() 调用会进入到这个锁的排队队列中,并会进入阻塞状态, 直到拿到锁以后才会返回。
若是不想阻塞,而是想尝试获取一下,若是锁被占用咱就不用,若是没被占用那就用, 这该怎么实现呢?可使用 pthread_mutex_trylock() 函数。 这个函数和 pthread_mutex_lock() 用法同样,只不过当请求的锁正在被占用的时候, 不会进入阻塞状态,而是马上返回,并返回一个错误代码 EBUSY,意思是说, 有其它线程正在使用这个锁。
int err = pthread_mutex_trylock(&mtx); if(0 != err) { if(EBUSY == err) { //The mutex could not be acquired because it was already locked. } }
若是不想不断的调用 pthread_mutex_trylock() 来测试互斥锁是否可用, 而是想阻塞调用,可是增长一个超时时间呢,那么可使用 pthread_mutex_timedlock() 来解决, 其调用方式以下:
struct timespec abs_timeout; abs_timeout.tv_sec = time(NULL) + 1; abs_timeout.tv_nsec = 0; int err = pthread_mutex_timedlock(&mtx, &abs_timeout); if(0 != err) { if(ETIMEDOUT == err) { //The mutex could not be locked before the specified timeout expired. } }
上面代码的意思是,阻塞等待线程锁,可是只等1秒钟,一秒钟后若是还没拿到锁的话, 那就返回,并返回一个错误代码 ETIMEDOUT,意思是超时了。
其中 timespec 定义在头文件 time.h 中,其定义以下
struct timespec { __time_t tv_sec; /* Seconds. */ long int tv_nsec; /* Nanoseconds. */ };
还须要注意的是,这个函数里面的时间,是绝对时间,因此这里用 time() 函数返回的时间增长了 1 秒。
用完了互斥锁,必定要记得释放,否则下一个想要得到这个锁的线程, 就只能去等着了,若是那个线程很不幸的使用了阻塞等待,那就悲催了。
释放互斥锁比较简单,使用 pthread_mutex_unlock() 便可:
pthread_mutex_unlock(&mtx);
经过 man pthread_mutex_destroy 命令能够看到 pthread_mutex_destroy() 函数的说明, 在使用此函数销毁一个线程锁后,线程锁的状态变为"未定义"。有的 pthread_mutex_destroy 实现方式,会使线程锁变为一个不可用的值。一个被销毁的线程锁能够被 pthread_mutex_init() 再次初始化。对被销毁的线程锁进行其它操做,其结果是未定义的。
对一个处于已初始化但未锁定状态的线程锁进行销毁是安全的。尽可能避免对一个处于锁定状态的线程锁进行销毁操做。
销毁线程锁的操做以下:
pthread_mutex_destroy(&mtx)
同步地址:https://www.fengbohello.top/archives/linux-pthread-mutex