概述

最常见的进程/线程的同步方法有互斥锁(或称互斥量Mutex),读写锁(rdlock),条件变量(cond),信号量(Semophore)等。在Windows系统中,临界区(Critical Section)和事件对象(Event)也是常用的同步方法。

 

简单的说,互斥锁保护了一个临界区,在这个临界区中,一次最多只能进入一个线程。如果有多个进程在同一个临界区内活动,就有可能产生竞态条件(race condition)导致错误。

 

读写锁从广义的逻辑上讲,也可以认为是一种共享版的互斥锁。如果对一个临界区大部分是读操作而只有少量的写操作,读写锁在一定程度上能够降低线程互斥产生的代价。

 

条件变量允许线程以一种无竞争的方式等待某个条件的发生。当该条件没有发生时,线程会一直处于休眠状态。当被其它线程通知条件已经发生时,线程才会被唤醒从而继续向下执行。条件变量是比较底层的同步原语,直接使用的情况不多,往往用于实现高层之间的线程同步。使用条件变量的一个经典的例子就是线程池(Thread Pool)了。

 

在学习操作系统的进程同步原理时,讲的最多的就是信号量了。通过精心设计信号量的PV操作,可以实现很复杂的进程同步情况(例如经典的哲学家就餐问题和理发店问题)。而现实的程序设计中,却极少有人使用信号量。能用信号量解决的问题似乎总能用其它更清晰更简洁的设计手段去代替信号量。 

 

本系列文章的目的并不是为了讲解这些同步方法应该如何使用(AUPE的书已经足够清楚了)。更多的是讲解很容易被人忽略的一些关于锁的概念,以及比较经典的使用与设计方法。文章会涉及到递归锁与非递归锁(recursive mutex和non-recursive mutex),区域锁(Scoped Lock),策略锁(Strategized Locking),读写锁与条件变量,双重检测锁(DCL),锁无关的数据结构(Locking free),自旋锁等等内容,希望能够抛砖引玉。

那么我们就先从递归锁与非递归锁说开去吧:)

 

1 可递归锁与非递归锁
1.1 概念

    在所有的线程同步方法中,恐怕互斥锁(mutex)的出场率远远高于其它方法。互斥锁的理解和基本使用方法都很容易,这里不做更多介绍了。

Mutex可以分为递归锁(recursive mutex)和非递归锁(non-recursive mutex)。可递归锁也可称为可重入锁(reentrant mutex),非递归锁又叫不可重入锁(non-reentrant mutex)。

二者唯一的区别是,同一个线程可以多次获取同一个递归锁,不会产生死锁。而如果一个线程多次获取同一个非递归锁,则会产生死锁。

Windows下的Mutex和Critical Section是可递归的。Linux下的pthread_mutex_t锁默认是非递归的。可以显示的设置PTHREAD_MUTEX_RECURSIVE属性,将pthread_mutex_t设为递归锁。

在大部分介绍如何使用互斥量的文章和书中,这两个概念常常被忽略或者轻描淡写,造成很多人压根就不知道这个概念。但是如果将这两种锁误用,很可能会造成程序的死锁。请看下面的程序。

MutexLock mutex;  void foo()
{  mutex.lock();  // do something  mutex.unlock();
}  void bar()
{  mutex.lock();  // do something  foo();  mutex.unlock();
}  

foo函数和bar函数都获取了同一个锁,而bar函数又会调用foo函数。如果MutexLock锁是个非递归锁,则这个程序会立即死锁。因此在为一段程序加锁时要格外小心,否则很容易因为这种调用关系而造成死锁。

    不要存在侥幸心理,觉得这种情况是很少出现的。当代码复杂到一定程度,被多个人维护,调用关系错综复杂时,程序中很容易犯这样的错误。庆幸的是,这种原因造成的死锁很容易被排除。

    但是这并不意味着应该用递归锁去代替非递归锁。递归锁用起来固然简单,但往往会隐藏某些代码问题。比如调用函数和被调用函数以为自己拿到了锁,都在修改同一个对象,这时就很容易出现问题。因此在能使用非递归锁的情况下,应该尽量使用非递归锁,因为死锁相对来说,更容易通过调试发现。程序设计如果有问题,应该暴露的越早越好。

 

1.2 如何避免

       为了避免上述情况造成的死锁,AUPE v2一书在第12章提出了一种设计方法。即如果一个函数既有可能在已加锁的情况下使用,也有可能在未加锁的情况下使用,往往将这个函数拆成两个版本---加锁版本和不加锁版本(添加nolock后缀)。

   例如将foo()函数拆成两个函数。

// 不加锁版本
void foo_nolock()
{  // do something
}
// 加锁版本
void foo()
{  mutex.lock();  foo_nolock();  mutex.unlock();
}  

递归锁的实例,在同一个线程中的递归锁

//线程属性
#include    <stdio.h>
#include    <stdlib.h>
#include    <pthread.h>
pthread_mutex_t g_mutex;
void test_fun(void);
static void thread_init(void)
{  //初始化锁的属性  pthread_mutexattr_t attr;  pthread_mutexattr_init(&attr);  pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP);//设置锁的属性为可递归  //设置锁的属性  pthread_mutex_init(&g_mutex, &attr);  //销毁  pthread_mutexattr_destroy(&attr);
}
//线程执行函数
void* thr_fun(void* arg)
{  int ret;  ret=pthread_mutex_lock(&g_mutex);  if( ret!=0 )  {  perror("thread  pthread_mutex_lock");  exit(1);  }  printf("this is a thread !/n");  test_fun();  ret=pthread_mutex_unlock(&g_mutex);  if( ret!=0 )  {  perror("thread  pthread_mutex_unlock");  exit(1);  }  return NULL;
}
//测试函数
void test_fun(void)
{  int ret;  ret=pthread_mutex_lock(&g_mutex);  if( ret!=0 )  {  perror("test pthread_mutex_lock");  exit(1);  }  printf("this is a test!/n");  ret=pthread_mutex_unlock(&g_mutex);  if( ret!=0 )  {  perror("test pthread_mutex_unlock");  exit(1);  }
}  int main(int argc, char *argv[])
{  int ret;  thread_init();  pthread_t tid;  ret=pthread_create(&tid, NULL, thr_fun, NULL);  if( ret!=0 )  {  perror("thread  create");  exit(1);  }  pthread_join(tid, NULL);  return 0;
}

执行结果为:

this is a thread !
this is a test!

详细说明:

类型互斥量属性控制着互斥量的特性。POSIX定义了四种类型。
enum
   {
     PTHREAD_MUTEX_TIMED_NP, 
     PTHREAD_MUTEX_RECURSIVE_NP, 
     PTHREAD_MUTEX_ERRORCHECK_NP,
     PTHREAD_MUTEX_ADAPTIVE_NP
   };
   其中,PTHREAD_MUTEX_TIMED_NP类型是标准(默认)的互斥量类型,并不作任何特殊的错误检查或死锁检查。PTHREAD_MUTEX_RECURSIVE_NP互斥量类型允许同一线程在互斥量解锁之前对该互斥量进行多次加锁。同一个递归互斥量维护锁的计数,在解锁的次数和加锁次数不同的情况下不会释放锁。即对同一互斥量加几次锁就要解几次锁。

涉及的函数
1.互斥量属性的初始化与回收
       #include <pthread.h>
       int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
       int pthread_mutexattr_init(pthread_mutexattr_t *attr);
       返回值:若成功返回0,否则返回错误编号。
2.获取/设置互斥量属性
       #include <pthread.h>
       int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr,
              int *restrict type);
       int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);
       返回值:若成功返回0,否则返回错误编号。
测试程序:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>pthread_mutex_t lock;
int g_val0, g_val1;int func(void)
{int ret, val;ret = pthread_mutex_lock(&lock);if (ret)printf("func:lock:%s\n", strerror(ret));val = g_val1+8;
#if 1ret = pthread_mutex_unlock(&lock);if (ret)printf("func:unlock%s\n", strerror(ret));
#endifreturn val;
}void * test0(void * arg)
{int ret;ret = pthread_mutex_lock(&lock);if (ret)printf("lock:%s\n", strerror(ret));sleep(5);g_val0 = func();printf("res=%d\n", g_val0);ret = pthread_mutex_unlock(&lock);if (ret)printf("unlock%s\n", strerror(ret));return NULL;
}void * test1(void * arg)
{sleep(1);#if 1int ret = pthread_mutex_lock(&lock);if (ret)printf("1:%s\n", strerror(ret));printf("g_val0=%d\n", g_val0);ret = pthread_mutex_unlock(&lock);if (ret)printf("1:unlock%s\n", strerror(ret));
#endifreturn NULL;
}int main(void)
{int ret;pthread_t tid[2];pthread_attr_t attr;pthread_mutexattr_t mutexattr;pthread_attr_init(&attr);pthread_mutexattr_init(&mutexattr);pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);pthread_mutexattr_settype(&mutexattr,PTHREAD_MUTEX_RECURSIVE_NP);pthread_mutex_init(&lock, &mutexattr);pthread_mutexattr_destroy(&mutexattr);ret = pthread_create(&tid[0], &attr, test0, NULL);if (ret) {fprintf(stderr, "create:%s\n", strerror(ret));exit(1);}ret = pthread_create(&tid[0], &attr, test1, NULL);if (ret) {fprintf(stderr, "create:%s\n", strerror(ret));exit(1);}    pthread_attr_destroy(&attr);pthread_exit(NULL);
}

线程同步----递归锁相关推荐

  1. Java核心(三)并发中的线程同步与锁

    2019独角兽企业重金招聘Python工程师标准>>> 乐观锁.悲观锁.公平锁.自旋锁.偏向锁.轻量级锁.重量级锁.锁膨胀...难理解?不存的!来,话不多说,带你飙车. 上一篇介绍了 ...

  2. Java 多线程和并发编程:(二)线程同步 Lock 锁

    线程同步 Lock 锁 1.Lock 锁 2.步骤 3.Lock 与 synchronized 的区别 1.Lock 锁 Lock 锁:对需要上锁的地方上锁 JDK1.5 后新增的功能 与 Synch ...

  3. iOS开发线程同步技术-锁

    概览 1,什么是锁(临界区)? 2,常用的锁有哪些? 3,相关链接 什么是锁(临界区) 临界区:指的是一块对公共资源进行访问的代码,并非一种机制或是算法. 常用的锁有哪些? 互斥锁:是一种用于多线程编 ...

  4. 线程同步(互斥锁、条件、读写锁、信号量)

    参考:(四十三)线程--线程同步(互斥锁.读写锁.条件变量.信号量) 作者:FadeFarAway 发布时间:2017-01-17 21:25:28 网址:https://blog.csdn.net/ ...

  5. 开线程插数据_python笔记7-多线程之线程同步(锁lock)

    前言丨 关于吃火锅的场景,小伙伴并不陌生,前面几章笔记里面我都有提到,今天我们吃火锅的场景: 吃火锅的时候a同学往锅里下鱼丸,b同学同时去吃掉鱼丸,有可能会导致吃到生的鱼丸. 为了避免这种情况,在下鱼 ...

  6. Linux多线程开发-线程同步-互斥锁pthread_mutex_t

    1.互斥锁 同一时刻只允许一个线程对临界区进行访问.POSIX库中用类型pthread_mutex_t来定义互斥锁,类型在pthreadtypes.h中定义. 2.如何声明一个互斥锁 #include ...

  7. C语言学习之线程同步——mutex锁、条件变量

    (一)mutex锁 #include <pthread.h> //静态初始化一个mutex类型的变量 pthread_mutex_t fastmutex = PTHREAD_MUTEX_I ...

  8. 递归锁、信号量、GIL锁、基于多线程的socket通信和进程池线程池

    递归锁.信号量.GIL锁.基于多线程的socket通信和进程池线程池 递归锁 死锁现象:是指两个或两个以上的进程和线程因抢夺计算机资源而产生的一种互相等待的现象 from threading impo ...

  9. java 多线程(四)—— 线程同步/互斥=队列+锁

    同步.异步.互斥的区别 在很多文章中,直接把同步缩小为互斥,并称之为同步.下面也是这样的. 一.线程同步 = 队列 + 锁 同步(这里说的其实是互斥)就是多个线程同时访问一个资源. 那么如何实现? 队 ...

最新文章

  1. 计算机二级没过学校要重修吗,如果学校说计算机二级没过不给发学位证改怎么办...
  2. 来自长辈的5句教导!
  3. SVN的VS.NET插件——AnkhSVN
  4. 45 张图深度解析 Netty 架构与原理
  5. 添加notepad到右键菜单栏
  6. 实现算法2.11、2.12的程序
  7. Pycharm虚拟环境的使用
  8. BAPI:BAPI_PRODORDCONF_CREATE_TT (TCODE:CO11N)
  9. Java 实现线性运动界面_java 实现顺序结构线性列表
  10. kafka的connect实现数据写入到kafka和从kafka写出
  11. Win10编译SqlCipher步骤
  12. java_home not found in your enviroment 问题解决方法
  13. xp系统下如何安装windows phone 7的软件xap文件
  14. 我从to B 角度看百度
  15. 12306bycloud,免费开源抢票软件,无需安装,全平台可用
  16. vue 登录 动态树 表格 cud
  17. 【洛谷 1516】青蛙的约会
  18. 通达信 数据格式 java_通达信日线 数据格式
  19. Cpu、核、Java Runtime.getRuntime().availableProcessors()
  20. php中 dsn什么意思,网络dsn是什么意思(图文)

热门文章

  1. 解决eclipse编译的几种方法
  2. allocWithZone
  3. MySQL 语句外键 连接
  4. MD5加密解密帮助类
  5. docker logstash log docker logs to elasticsearch
  6. SUDO的环境变量为何不同?
  7. 用基本控件简单地仿QQ登录界面
  8. gevent queue应用1
  9. RHEL6的系统开机的过程
  10. SQL查询前10条记录(SqlServer/mysql/oracle)[语法分析]