码迷,mamicode.com
首页 > 其他好文 > 详细

保护临界资源——互斥锁

时间:2021-02-03 11:03:42      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:过多   变量   oid   修改   返回   最简   error   slist   border   

1、概述

  互斥锁通常用在多线程中,用于保护临界资源。什么是临界资源?我的理解就是有可能被多个线程同时占用的资源,比如线程1要使用一个全局变量的时候,这时调度到了线程2,线程2改变了这个全局变量的值,这时线程1再去使用这个全局变量的时候就可能出问题。举个现实生活中的例子,A要用打印机打印很多资料,B也要用打印机,如果A在用打印机的时候B也用了打印机,这时A去取他打印的东西的时候他会发现里面掺杂了B打印的东西。所以A在使用打印机的时候,他希望别人不要使用打印机,所以他就将打印机锁起来,等他把东西打印完之后他在将所打开。

2、函数介绍

2.1 初始化互斥锁

2.1.1 pthread_mutex_init

函数原型

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,则初始化的就是普通锁。

2.2 上锁

2.2.1 pthread_mutex_lock

函数原型

int pthread_mutex_lock(pthread_mutex_t *mutex);

头文件 pthread.h
功能 获得互斥锁资源(上锁),获得资源成功后,其他线程将不能再获得资源,除非该线程释放互斥锁资源(解锁)。获取不到资源的线程在调用该函数时会阻塞。
参数 [in]:mutex:互斥锁
返回 成功返回0,失败返回错误码

2.2.2 pthread_mutex_trylock

函数原型 int pthread_mutex_trylock(pthread_mutex_t *mutex);
头文件 pthread.h
功能 尝试获得锁资源。该函数不会阻塞。
参数 [in]:mutex:互斥锁
返回 如果成功则返回0,失败则返回错误码,错误码为EBUSY(值为16)表示锁被占用。

 

2.3 解锁

2.3.1 pthread_mutex_unlock

函数原型

int pthread_mutex_unlock(pthread_mutex_t *mutex);

头文件 pthread.h
功能 释放锁资源
参数 [in]:mutex:互斥锁
返回 成功返回0,失败返回错误码

2.4 销毁锁

2.4.1 pthread_mutex_destroy

函数原型 int pthread_mutex_destroy(pthread_mutex_t *mutex);
头文件 pthread.h
功能 销毁互斥锁
参数 [in]:mutex:互斥锁
返回 成功返回0,失败返回错误码

3、测试程序

常规测试代码如下:

 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,结果并未发生改变,这与“当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性”这句话不符,目前尚未找到原因。

4、其他测试

  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

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!