前阵子写静态lib导出单实例多线程安全API时,出现了CRITICAL_SECTION初始化太晚的问题,之后查看了错误的资料,引导向了错误的理解,以至于今天凌晨看到另一份代码,也不多想的以为singletone double check会出bug,本文做下记录备忘。

  相关知识点:Singleton Double Check、多线程下的局部Static对象、静态Lib中的全局对象。

  一、singleton double check

  SingleInstance* volatile g_instance = NULL;

  cswuyg::MyCritical g_cs;

  SingleInstance* GetInstance()

  {

  if (g_instance == NULL)

  {

  cswuyg::Lock<> lock(g_cs);

  if (g_instance == NULL)

  {

  g_instance = new SingleInstance;

  }

  }

  return g_instance;

  }

  这样的代码一般(不考虑全局对象的初始化)没有问题。之前只略看他人的文章,不思考,误以为:g_instance = new SingleInstance ; 这句在线程A的执行会被线程B g_instance == NULL的判断打断,导致线程B返回的g_instance是一个半成品。实际上不会,因为g_instance的赋值是在内存分配、构造函数执行之后做的,而且赋值是原子操作,这没有问题。

  按照文档的说法,g_instance变量应该加上volatile,避免编译器优化,编译器优化之后,可能会导致g_instance变量的赋值在SingleInstance构造函数执行之前。volatile用于表明这个变量是易变的,每一次都直接操作对应内存,而不是用寄存器缓存,不会去优化指令。这里如果不使用它,就可能导致编译器调整汇编指令的顺序,分配完内存就直接把地址赋值给g_instance指针,后面再调用构造函数,它这样调整的理由可能是这样子:分配到的内存指针在后续的执行中没有被修改,先赋值给g_instance和晚赋值给g_instance没有区别。

  二、导出Lib中慎用全局对象

  我的Lib的导出API提供的数据只需要获取一次就够了,不能多次获取,所以它必须是单实例的、多线程安全的,再考虑到不能浪费频繁的锁消耗,很直接的做法便是用singleton double check。

  首先我选择使用临界区实现锁,而临界区在API被调用之前需要先初始化,于是定义一个Lock封装了临界区的初始化,什么时候初始化?必须是全局对象,如果为定义局部static对象会导致多线程不安全托福答案 www.qcwy123.com

static对象不是多线程安全的:

  从上图的汇编指令可以看到static对象的构造函数是否被执行的判断逻辑:

  1、通过标识值判断是否该执行构造函数(这里的构造函数内联了);

  2、执行构造函数,首先把标志值置位。

  有可能多个线程都同时通过了1的判断,导致构造函数被多次执行。

  使用了全局对象之后发现也不可行:导出函数依赖全局对象的初始化,虽然全局对象会在main函数之前初始化,但初始化时机还是可能太晚了,譬如这种情况:lib的使用者也定义了全局对象,并且初始化得更早,使用者的全局对象构造函数里调用了lib的导出函数,导出函数使用了还没初始化的临界区全局对象导致崩溃,更麻烦的是,使用者的dump捕获机制是在main函数里初始化的,生效得太晚,导致dump无法捕获,使这个crash更加隐蔽。C++的全局对象应该尽量少用。exe里面如果使用了全局对象,则需要保证dump捕获机制对所有的代码都生效。

  既然临界区初始化问题无法解决,局部static对象、全局对象都无法使用,需要找到一个不需要初始化又能实现锁的方法:那就是原子操作。

  单纯的原子操作并没有锁的功能,需要配合上:if + Sleep.

  代码如下:

  SingleInstance* volatile g_instance;

  LONG volatile g_for_lock;

  SingleInstance* GetInstance()

  {

  if (g_instance == NULL)

  {

  LONG pre_value = ::InterlockedExchange(&g_for_lock, 1);

  if (pre_value != 0)

  {

  while(g_instance == NULL)

  {

  ::Sleep(55);

  }

  }

  if (g_instance == NULL)

  {

  g_instance = new SingleInstance;

  }

  }

  return g_instance;

  }

  全局的g_for_lock在PE文件装入内存时就初始化为0,所以不存在初始化问题;InterlockedExchange 适用于xp、win7、win8,不存在系统限制;多个线程同时调用InterlockedExchange,只能有一个线程得到0,保证只初始化一次,其余线程进入while循环等待,直到g_point非空。问题不逼你,你就不会想到还有这么好的实现思路托福答案 www.tfjy386.com

  使用原子操作还可以很容易的实现临界区锁的功能,这里就不说了。

  三、PE文件中的Lib库全局变量

  像上边定义的全局变量,如果DLL和EXE都使用这个lib,它们各自有一份独立的全局变量。

实现单实例多线程安全API问题的更多相关文章

  1. Servlet单实例多线程模式

    http://kakajw.iteye.com/blog/920839 前言:Servlet/JSP技术和ASP.PHP等相比,由于其多线程运行而具有很高的执行效率.由于Servlet/JSP默认是以 ...

  2. Servlet 生命周期、工作原理-是单实例多线程

    Servelet是单实例多线程的 参考:servlet单实例多线程模式 一.Servlet生命周期 大致分为4部:Servlet类加载-->实例化-->服务-->销毁 1.Web C ...

  3. Java ,单实例 多线程 ,web容器,servlet与struts1-2.x系列,线程安全的解决

    1.Servlet是如何处理多个请求同时访问呢? 回答:servlet是默认采用单实例,多线程的方式进行.只要webapp被发布到web容器中的时候,servlet只会在发布的时候实例化一次,serv ...

  4. Singleton、MultiThread、Lib——实现单实例无锁多线程安全API

        前阵子写静态lib导出单实例多线程安全API时,出现了CRITICAL_SECTION初始化太晚的问题,之后查看了错误的资料,引导向了错误的理解,以至于今天凌晨看到另一份代码,也不多想的以为s ...

  5. Servlet 单例多线程

    Servlet如何处理多个请求访问? Servlet容器默认是采用单实例多线程的方式处理多个请求的: 1.当web服务器启动的时候(或客户端发送请求到服务器时),Servlet就被加载并实例化(只存在 ...

  6. servlet单例多线程

    Servlet如何处理多个请求访问? Servlet容器默认是采用单实例多线程的方式处理多个请求的: 1.当web服务器启动的时候(或客户端发送请求到服务器时),Servlet就被加载并实例化(只存在 ...

  7. Servlet 单例多线程【转】

    源地址:Servlet 单例多线程 Servlet如何处理多个请求访问?Servlet容器默认是采用单实例多线程的方式处理多个请求的:1.当web服务器启动的时候(或客户端发送请求到服务器时),Ser ...

  8. [转]Servlet 单例多线程

    Servlet如何处理多个请求访问? Servlet容器默认是采用单实例多线程的方式处理多个请求的: 1.当web服务器启动的时候(或客户端发送请求到服务器时),Servlet就被加载并实例化(只存在 ...

  9. Servlet 单例多线程详解(六)

    一.Servlet 单例多线程 Servlet如何处理多个请求访问?Servlet容器默认是采用单实例多线程的方式处理多个请求的:1.当web服务器启动的时候(或客户端发送请求到服务器时),Servl ...

随机推荐

  1. Linux&shell之如何控制脚本

    写在前面:案例.常用.归类.解释说明.(By Jim) Ctrl+C组合键可以生产SIGINT信号Ctrl+Z组合键生产SIGTSTP信号,停止进程后程序仍然留在内存中,能够从停止的地方继续运行. 捕 ...

  2. BZOJ1524: [POI2006]Pal

    1524: [POI2006]Pal Time Limit: 5 Sec  Memory Limit: 357 MBSubmit: 308  Solved: 101[Submit][Status] D ...

  3. Spark SQL Table Join(Python)

    示例   Spark SQL注册“临时表”执行“Join”(Inner Join.Left Outer Join.Right Outer Join.Full Outer Join)   代码   fr ...

  4. oracle事务和锁

    数据库事务概括 1. 说明 一组SQL,一个逻辑工作单位,执行时整体修改或者整体回退. 2.事务相关概念 1)事务的提交和回滚:COMMIT/ROLLBACK 2)事务的开始和结束 开始事务:连接到数 ...

  5. 【Android官方Training教程】Getting Started部分学习笔记

    Getting Started Welcome to Training for Android developers. Here you'll find sets of lessons within ...

  6. 甲骨文公司 Oracle

    甲骨文公司 甲骨文公司,全称甲骨文股份有限公司,是全球最大的企业软件公司,总部位于美国加利福尼亚州的红木滩.甲骨文是继Microsoft之后,全球收入第二多的软件公司.甲骨文公司1989年正式进入中国 ...

  7. linux/hpux 添加用户

    #添加用户组, 组名dev, 组id为1001groupadd -g 1001 dev #添加用户组dev,不指定组idgroupadd dev #添加用户user1, id为111, 属于dev组, ...

  8. EMV/PBOC 解析(二) 卡片数据读取

    上一篇简单的了解了IC智能卡的文件结构和APDU报文,这篇我们直接来读取卡内的数据.下面我们主要参照<中国金融集成电路(IC)卡规范>. 好了废话不多说,下面贴指令: (1)卡片接收一个来 ...

  9. 跟Google学习Android开发-起始篇-构建你的第一个应用程序(4)

    说明:此系列教程翻译自Google Android开发者官网的Training教程,利用Chome浏览器的自动翻译功能作初译,然后在一些语句不顺或容易造成误解的地方作局部修正.方便英文不好的开发者查看 ...

  10. JStorm 是一个分布式实时计算引擎

    alibaba/jstorm JStorm 是一个分布式实时计算引擎. JStorm 是一个类似Hadoop MapReduce的系统, 用户按照指定的接口实现一个任务,然后将这个任务递交给JStor ...