zoukankan      html  css  js  c++  java
  • stl空间配置器线程安全问题补充

    摘要

    在上一篇博客《STL空间配置器那点事》简单介绍了空间配置器的基本实现

    两级空间配置器处理,一级相关细节问题,同时简单描述了STL各组件之间的关系以及设计到的设计模式等。

    在最后,又关于STL空间配置的效率以及空间释放时机做了简单的探讨。

    线程安全问题概述

    为什么会有线程安全问题?

      认真学过操作系统的同学应该都知道一个问题。

      first--进程是系统资源分配和调度的基本单位,是操作系统结构的基础,是一个程序的运行实体,同时也是一个程序执行中线程的容器

      seconed--进程中作为资源分配基本单位,管理着所有线程共享资源:代码段,数据段,堆,部分共享区(IPC中的共享内存等)。。栈则是线程私有的。

    所以,由此就有:如果我们的数据存放位置处在数据段,堆这两个地方,那么就会有线程安全问题:

     1 #include <iostream>
     2 using namespace std;
     3 static int * arr = new int(4);     //arr作为全局变量存在于数据段,new申请所得空间存在于堆上。
     4 
     5 void testThreadSafe(int arg)
     6 {
     7     *arr = arg;
     8 }
     9 
    10 int main()
    11 {
    12     int arg;
    13     cin >> arg;
    14     testThreadSafe(arg);
    15     cout << (*arr)<<endl;
    16     return 0;
    17 }

      做个简单分析,假设进程同时运行到了第七行,因为程序执行的最小粒度是更为细致的cpu指令而不是一个代码语句。

    所以可能A线程和B线程同时执行修改*arr = arg;,但是两个线程中cin>>arg输入的值不一样,那么就有问题。

    两个线程各自执行到15行时,显示的结果是一样的(因为线程共享该区域),但他们本来却不该相同。

    这就是线程安全问题。

    STL中线程安全问题的存在  

    STL中,一级空间配置器简单封装malloc,free同时引入sethandler机制。而malloc,free作为最基本的系统调用是线程安全的,
    所以问题就在二级空间配置器的实现部分了。

      各位还记得二级配置器内部结构定义吧。

    template <bool threads, int inst>
    class __DefaultAllocTemplate 
    {
    //...
    protected:
    
    //桶结构,保存链表
        static _Obj* _freeList[_NFREELISTS]; 
    //.....
    };

    这里的核心结构,保存自由链表的指针数组就是各静态数据,存在于数据段,于是就有了线程安全问题。

    线程安全问题的解决方案之一:

    linux环境,互斥锁

    win环境,临界区(临界资源访问问题)

      对于STL的二级空间配置器中,线程安全问题的唯一存在也就是对于已组织的自由链表的访问了(也就是Allocate和Deallocate了):
    两个线程同时向空间配置器申请内存块(ps,A未完成取出该节点并将表指针指向下一个节点时,B线程来了。于是两个线程同时得到一块内存);

    //////A执行玩1,尚未执行2,B就来申请空间。最终两个线程都修改数组中指针指向y,且共同拥有x

    两个线程同时向空间配置器释放内存块;

      

    ////a释放执行1而没有来得及执行2,于是乎,在1。5的情况系,b释放,进入。于是,最终结果,a块,b块都指向了x,但是数组中指针只是指向了后来修改他的值,于是就有了内存泄漏。

    解决方案,互斥锁使用

    核心代码给出:

    文件Alloc.h中部分代码

    #pragma once
    
    #include "Config.h"
    #include "Trace.h"
    
    #include "Threads.h"
    
    #ifdef __STL_THREADS
    #define __NODE_ALLOCATOR_THREADS true  //用于二级空间配置器翻非类型模板参数
    
    #define __NODE_ALLOCATOR_LOCK 
            { if (threads) _S_node_allocator_lock._M_acquire_lock(); }
    #define __NODE_ALLOCATOR_UNLOCK 
            { if (threads) _S_node_allocator_lock._M_release_lock(); }
    #else
    //  Thread-unsafe
    #   define __NODE_ALLOCATOR_LOCK
    #   define __NODE_ALLOCATOR_UNLOCK
    #   define __NODE_ALLOCATOR_THREADS false
    #endif
    
    
    
    
    # ifdef __STL_THREADS
        static _STL_mutex_lock _S_node_allocator_lock;
    # endif
    
    template <bool threads, int inst>
    class __DefaultAllocTemplate 
    {
    
    class _Lock;
        friend class _Lock;
        class _Lock {
            public:
                _Lock() 
                {
                    __TRACE("锁保护
    ");
                 __NODE_ALLOCATOR_LOCK;
                 }
                ~_Lock()
                {
                    __TRACE("锁撤销
    ");
                 __NODE_ALLOCATOR_UNLOCK; 
                 }
        };
    static void* Allocate(size_t n)
        {
            void * ret = 0;
            __TRACE("二级空间配置器申请n = %u
    ",n);
            if(n>_MAX_BYTES)
                ret = MallocAlloc::Allocate(n);
    
            _Obj* volatile * __my_free_list = _freeList + _FreeListIndex(n);
    
            //利用RAII(资源获取即初始化原则)进行封装,保证 即使内部抛出异常,依旧执行解锁操作
    #ifdef __STL_THREADS
              _Lock __lock_instance;
    #endif
            _Obj* __result = *__my_free_list;
        
            if (__result == 0)
                ret = _Refill(RoundUp(n));
            else
            {
                *__my_free_list = __result -> _freeListLink;
                ret = __result;
            }
            return ret;
        }
    
        static void Deallocate(void* p, size_t n)
        {
            if(!p)
            {
                return;
            }
            __TRACE("二级空间配置器删除p = %p,n = %d
    ",p,n);
            if (n > (size_t) _MAX_BYTES)
                MallocAlloc::Deallocate(p, n);
            else
            {
                _Obj* volatile*  __my_free_list = _freeList + _FreeListIndex(n);
                _Obj* q = (_Obj*)p;
            
    #ifdef __STL_THREADS
                //进行资源归还自由链表时的锁操作。
                  _Lock __lock_instance;
    #endif
                q -> _freeListLink = *__my_free_list;
                *__my_free_list = q;
            }
        }

    文件Threads.h

    #pragma once 
    
    #if defined(__STL_PTHREADS)
    #include <pthread.h>
    #endif
    
    #include "Config.h"
    
    __STLBEGIN
    
    struct _STL_mutex_lock
    {
    
    #if defined(__STL_PTHREADS)
      pthread_mutex_t _M_lock;
      void _M_initialize()   { pthread_mutex_init(&_M_lock, NULL); }
      void _M_acquire_lock() { pthread_mutex_lock(&_M_lock); }
      void _M_release_lock() { pthread_mutex_unlock(&_M_lock); }
    #else /* No threads */
      void _M_initialize()   {}
      void _M_acquire_lock() {}
      void _M_release_lock() {}
    #endif
    };
    
    __STLEND

    简单测试结果

    其中TRACE打印的“锁保护”,“锁撤销” 部分就是二级空间配置器资源分配时锁机制的保护实现了。

      其利用了C++的RAII(资源获取即初始化方案)  

      同时利用C++对象特性,退出作用域即执行析构函数,将解锁封装,巧妙的避免了死锁问题的产生

    死锁:简单理解就是,因为某个线程锁定资源进行访问时,因为异常等原因退出执行,但是没来的及解锁,致使其他线程都无法访问共享资源的现象就是死锁。更细致的解释请找google叔。

    最后,说明的是,实际的STL源码中因为需要考虑平台,系统兼容性等问题,对于锁的使用通过宏编译技术,有比较长的一段代码,我这里只是取出了当下linux平台可用代码放在了自己的Threads.h

    更详细代码请关注个人另一博客:http://www.cnblogs.com/lang5230/p/5556611.html

    或者github获取更新中的代码:https://github.com/langya0/llhProjectFile/tree/master/STL

  • 相关阅读:
    tableau学习笔记—1
    sql学习笔记1
    rpy2的安装问题?【解决】
    python_广州房价热力图
    数据清洗记录——.图书馆学号去敏
    python argparse
    利用torch.nn实现前馈神经网络解决 多分类 任务
    R7000P Ubuntu20.04 安装 Realtek 8852 无线网卡驱动
    邻接矩阵的相乘的意义
    分类问题中评价指标
  • 原文地址:https://www.cnblogs.com/lang5230/p/5576336.html
Copyright © 2011-2022 走看看