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("锁保护\n");
             __NODE_ALLOCATOR_LOCK;
             }
            ~_Lock()
            {
                __TRACE("锁撤销\n");
             __NODE_ALLOCATOR_UNLOCK;
             }
    };
static void* Allocate(size_t n)
    {
        void * ret = 0;
        __TRACE("二级空间配置器申请n = %u\n",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\n",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

时间: 2024-11-03 18:22:35

stl空间配置器线程安全问题补充的相关文章

STL空间配置器那点事

STL简介 STL(Standard Template Library,标准模板库),从根本上说,STL是一些“容器”的集合,这些“容器”有list,vector,set,map等,STL也是算法和其他一些组件的集合. 谈及组件,那么我们就首先来简单谈下STL六大组件,其相关的设计模式使用,以及各组件之间的协作关系. 设计模式一览 六大组件简单介绍 1. 空间配置器:内存池实现小块内存分配,对应到设计模式--单例模式(工具类,提供服务,一个程序只需要一个空间配置器即可),享元模式(小块内存统一由

STL空间配置器

1.什么是空间配置器? 空间配置器负责空间配置与管理.配置器是一个实现了动态空间配置.空间管理.空间释放的class template.以内存池方式实现小块内存管理分配.关于内存池概念可以点击:内存池. 2.STL空间配置器产生的缘由 在软件开发,程序设计中,我们不免因为程序需求,使用很多的小块内存(基本类型以及小内存的自定义类型).在程序中动态申请,释放.这个过程过程并不是一定能够控制好的,于是乎出现以下问题: 问题1:就出现了内存碎片问题.(ps外碎片问题) 问题2:一直在因为小块内存而进行

stl 空间配置器理解

理解了一下stl的空间配置器,发现一个比较好的学习方法,跟着代码自己也跟着写一遍,顺便加些注释,可以更加帮助自己理解. 如new,delete一般,分为两个步骤,1,配置空间,2,构造对象(1,析构对象,2,释放空间) 一.构造和析构的基本工具(construct,destroy) 1,construct(构造) template<class T1,class T2> inline void construct(T1 * p,const T2 & value){ new (p) T1(

STL——空间配置器(SGI-STL)

一. 空间配置器标准接口 参见<STL源码剖析>第二章-2.1. 二.具备次配置力的SGI空间配置器 SGI STL的配置器与众不同,也与标准规范不同,其名称是alloc而非allocator,而且不接受任何参数(虽然SGI也定义有一个符合部分标准.名为sllocator的配置器,但SGI自己从未用过它,也不建议使用,主要因为效率不佳).这并不会带来什么困扰:我们通常很少需要自行指定配置器名称,而SGI STL的每一个容器都已经指定其缺省的空间配置器为alloc. // 在程序中要明白采用SG

stl空间配置器alloc

SGI设计了双层级配置器,第一级配置器直接使用malloc()和free(),第二级配置器视情况采用不同的策略:当配置区块超过128bytes时,调用第一级配置器,当配置区块小于128bytes时,采用内存池方式 //SGI第一级配置器 template<int inst> class __malloc_alloc_template { private: //处理内存不足的情况 //c++ new handler机制:系统在内存配置需求无法被满足时,调用一个指定函数,参见effective c

STL空间配置器(一)

STL空间适配器(一) Author:胡建 Time:2016/4/5 这是STL学习的第一部分,空间适配器,所谓空间适配器,就是用来管理内存的一个器具.对于STL来说,空间适配器是它可以正常工作的基础,也为它可以高效工作提供了动力.对于使用STL来说,它是不和用户直接打交道的,而是隐藏在一切STL组建之后,默默为各种内存申请提供支持的. 对于c++用户来说,new和delete很熟悉,这两个函数可以分别完成内存的申请和释放,和c里面的malloc和free如出一辙,SGI 有一个标准空间适配器

STL空间配置器、vector、list、deque、map复习

本文写于2017-03-03,从老账号迁移到本账号,原文地址:https://www.cnblogs.com/huangweiyang/p/6440830.html STL的六大组件:容器.算法.迭代器.空间配置器.容器适配器.仿函数. 空间配置器 空间配置器产生的缘由:由于程序需求,很多小块内存在程序中动态申请.释放.于是就容易出现内存外部碎片问题,同时由于一直调用malloc系统调用,产生性能问题. (注:内碎片:因为内存对齐/访问效率而差生如用户需要3字节,实际得到4字节的问题,其中的碎片

STL——空间配置器

__malloc_alloc_template分配器:该分配器是对malloc.realloc以及free的封装: 当调用malloc和realloc申请不到内存空间的时候,会改调用oom_malloc()和oom_realloc(),这两个函数会反复调用用户传递过来的out of memory handler处理函数,直到能用malloc或者realloc申请到内存为止. 如果用户没有传递__malloc_alloc_oom_handler,__malloc_alloc_template会抛出

STL 之 空间配置器(allocator)

一.SGI 标准的空间配置器,std::allocator SGI也定义了一个符合部分标准,名为allocator的配置器,但是它自己不使用,也不建议我们使用,主要原因是效率不佳. 它只是把C++的操作符::operator new和::operator delete做了一层简单的封装而已. 二.SGI 特殊的空间配置器,std::alloc 由于SGI 标准的空间配置器只是把C++的操作符::operator new和::operator delete做了一层简单的封装,没有考虑到任何效率上的