标签:过多 变量 oid 修改 返回 最简 error slist border
互斥锁通常用在多线程中,用于保护临界资源。什么是临界资源?我的理解就是有可能被多个线程同时占用的资源,比如线程1要使用一个全局变量的时候,这时调度到了线程2,线程2改变了这个全局变量的值,这时线程1再去使用这个全局变量的时候就可能出问题。举个现实生活中的例子,A要用打印机打印很多资料,B也要用打印机,如果A在用打印机的时候B也用了打印机,这时A去取他打印的东西的时候他会发现里面掺杂了B打印的东西。所以A在使用打印机的时候,他希望别人不要使用打印机,所以他就将打印机锁起来,等他把东西打印完之后他在将所打开。
函数原型 |
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr); |
头文件 | pthread.h |
功能 | 初始化一个互斥锁 |
参数 |
[out]:mutex:要初始化的互斥锁 [in]:attr:互斥锁属性,见具体描述 |
返回 |
成功返回0,失败返回错误码 |
该函数声明中restrict 是C99标准中新增的关键字,用来优化指针的,可以忽略不计。调用该函数之前,先定义一个pthread_mutex_t类型的互斥锁,pthread_mutex_t结构定义如下:
/* 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; /* KIND must stay at this position in the structure to maintain binary compatibility. */ int __kind; unsigned int __nusers; __extension__ union { int __spins; __pthread_slist_t __list; }; } __data; char __size[__SIZEOF_PTHREAD_MUTEX_T]; long int __align; } pthread_mutex_t;
这是个共用体,其实根本不用管里面都是啥,接下来看pthread_mutexattr_t类型的定义:
typedef union { char __size[__SIZEOF_PTHREAD_MUTEXATTR_T]; long int __align; } pthread_mutexattr_t;
其中__SIZEOF_PTHREAD_MUTEXATTR_T的值就是4,整个共用体占的字节大小也是4。之所以将该结构拿出来,是因为在使用的时候通常会使用下面4个值:
PTHREAD_MUTEX_TIMED_NP,值为0,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
PTHREAD_MUTEX_RECURSIVE_NP,值为1,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
PTHREAD_MUTEX_ERRORCHECK_NP,值为2,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样保证当不允许多次加锁时不出现最简单情况下的死锁。
PTHREAD_MUTEX_ADAPTIVE_NP,值为3,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
(下划线部分摘自https://www.cnblogs.com/eustoma/p/10054783.html)
由于第二个参数attr是一个共用体的指针,而上述4个值是枚举类型的值,因此不能直接赋值,可以通过强制类型转换赋值。如果attr传入NULL,则初始化的就是普通锁。
函数原型 |
int pthread_mutex_lock(pthread_mutex_t *mutex); |
头文件 | pthread.h |
功能 | 获得互斥锁资源(上锁),获得资源成功后,其他线程将不能再获得资源,除非该线程释放互斥锁资源(解锁)。获取不到资源的线程在调用该函数时会阻塞。 |
参数 | [in]:mutex:互斥锁 |
返回 | 成功返回0,失败返回错误码 |
函数原型 | int pthread_mutex_trylock(pthread_mutex_t *mutex); |
头文件 | pthread.h |
功能 | 尝试获得锁资源。该函数不会阻塞。 |
参数 | [in]:mutex:互斥锁 |
返回 | 如果成功则返回0,失败则返回错误码,错误码为EBUSY(值为16)表示锁被占用。 |
函数原型 |
int pthread_mutex_unlock(pthread_mutex_t *mutex); |
头文件 | pthread.h |
功能 | 释放锁资源 |
参数 | [in]:mutex:互斥锁 |
返回 | 成功返回0,失败返回错误码 |
函数原型 | int pthread_mutex_destroy(pthread_mutex_t *mutex); |
头文件 | pthread.h |
功能 | 销毁互斥锁 |
参数 | [in]:mutex:互斥锁 |
返回 | 成功返回0,失败返回错误码 |
常规测试代码如下:
1 #include <stdio.h> 2 #include <pthread.h> 3 #include <errno.h> 4 5 #define USE_MUTEX /* 使用互斥锁 */ 6 7 /* 定义全局变量 */ 8 int a = 0; 9 pthread_mutex_t mutex; /* 互斥锁 */ 10 11 void *func1(void *p_arg) 12 { 13 int b = 0; 14 int ret = 0; 15 16 pthread_detach(pthread_self()); 17 18 19 while (1) { 20 #ifdef USE_MUTEX 21 ret = pthread_mutex_lock(&mutex); 22 printf("lock ret = %d\n", ret); 23 #endif 24 /////////////////////// 25 a = 2; 26 sleep(1); /* 延时1秒,模拟复杂的计算 */ 27 b = a + 1; 28 /////////////////////// 29 30 printf("b = %d\n", b); 31 32 #ifdef USE_MUTEX 33 ret = pthread_mutex_unlock(&mutex); 34 printf("unlock ret = %d\n", ret); 35 #endif 36 37 sleep(1); 38 } 39 } 40 41 void *func2(void *p_arg) 42 { 43 int c = 0; 44 int ret = 0; 45 46 pthread_detach(pthread_self()); 47 48 49 while (1) { 50 #ifdef USE_MUTEX 51 ret = pthread_mutex_lock(&mutex); 52 printf("lock ret = %d\n", ret); 53 #endif 54 /////////////////////// 55 a = 3; 56 sleep(1); /* 延时1秒,模拟复杂的计算 */ 57 c = a * 2; 58 /////////////////////// 59 60 printf("c = %d\n", c); 61 62 #ifdef USE_MUTEX 63 ret = pthread_mutex_unlock(&mutex); 64 printf("unlock ret = %d\n", ret); 65 #endif 66 sleep(1); 67 } 68 } 69 70 int main(int argc, const char *argv[]) 71 { 72 int ret = 0; 73 pthread_t thread[2]; 74 75 /* 初始化互斥锁 */ 76 ret = pthread_mutex_init(&mutex, NULL); 77 if (ret != 0) { 78 printf("pthread_mutex_init error\n"); 79 return 0; 80 } 81 82 /* 创建线程 */ 83 pthread_create(&thread[0], NULL, func1, NULL); 84 pthread_create(&thread[1], NULL, func2, NULL); 85 86 while (1) { 87 sleep(1); 88 } 89 90 pthread_mutex_destroy(&mutex); 91 92 return 0; 93 }
运行结果:
代码分析:
a是一个全局变量,两个线程中都使用了a这个全局变量,但是使用互斥锁和不使用互斥锁打印的b和c的结果不一样,其实不难分析出来,如果不使用互斥锁的话,在sleep期间另一个线程改变了a的值,返回原线程的时候由于a的值被改变了,因此计算结果就和预期不一样。如果使用互斥锁,在一个线程获得锁之后,另一个线程想要获得锁就得等该线程释放锁才行。程序中使用sleep(1)是用来模拟复杂的计算过程,表明如果一个临界资源要被一个线程占用很久的话,此时很有可能会发生调度,导致临界资源被其他线程改变,这样就会使结果与预期不符。实际上别说是复杂的计算过程了,就算是很简单的代码都有可能会被打断,因此无论是多简单的代码,只要涉及到临界资源,都应该要加锁。
上述例程是一个非常典型的应用,初始化互斥锁传入的第二个参数为NULL,表示普通锁,那么想要获取锁资源的线程应该形成一个等待队列,那么两个线程应该会交替执行,但是如果注释掉第37行和第66行的sleep(1)之后,运行结果是只打印c的值,不会打印b的值,表明线程2在释放锁资源之后,由while循环又继续获得了锁资源,这明显是不公平的。即便在初始化互斥锁时指定属性为PTHREAD_MUTEX_TIMED_NP,结果并未发生改变,这与“当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性”这句话不符,目前尚未找到原因。
2.1.1节中介绍了4种不同的锁,他们由使用pthread_mutex_init初始化锁时传入的第2个参数决定,下面再次将这些锁的特性列出来,并与实际测试值结果作比较。
PTHREAD_MUTEX_TIMED_NP,值为0,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
PTHREAD_MUTEX_RECURSIVE_NP,值为1,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
PTHREAD_MUTEX_ERRORCHECK_NP,值为2,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样保证当不允许多次加锁时不出现最简单情况下的死锁。
PTHREAD_MUTEX_ADAPTIVE_NP,值为3,适应锁。动作最简单的锁类型,仅等待解锁后重新竞争。
经过测试发现普通锁与适应锁是一样的,普通锁并不会将请求锁的线程形成一个等待队列,它也是重新竞争。
如果一个互斥锁为普通锁或适应锁,那么该线程在获取到锁资源后必须避免再次调用pthread_mutex_lock,因为pthread_mutex_lock会导致线程阻塞,而锁资源又没有释放,那么此时pthread_mutex_lock便会一直阻塞,这便是传说中的死锁。为了避免死锁,可以将锁初始化为嵌套锁或者检错锁。
如果一个互斥锁为嵌套锁,那么当一个线程获得锁资源之后,它再次调用pthread_mutex_lock不会阻塞,并且返回值也为0,这个效果相当于又上了一次锁,只有当它解掉所有的锁之后其他线程才能竞争。就好比如两个人去争一个打印机,一旦A争到了打印机,他可以给这个打印机上很多把锁,只有当A把打印机上所有的锁都解开,其他人才能参与竞争打印机。
如果一个互斥锁为检错锁,当一个线程获得锁资源之后,如果它再次调用pthread_mutex_lock则会返回EDEADLK,其值为35,解释为“Resource deadlock would occur”告知会发生死锁。
注意嵌套锁和检错锁获得锁资源的线程再次调用pthread_mutex_lock都不会引起阻塞,这并不是说嵌套锁和检错锁不会阻塞,它是在获得锁资源的线程中不阻塞,但在没有获得锁资源的线程中还是阻塞的。
互斥锁还有很多别的内容,比如修改锁的属性等,这些内容都不常用,这里就不说了。
标签:过多 变量 oid 修改 返回 最简 error slist border
原文地址:https://www.cnblogs.com/Suzkfly/p/14363619.html