stl空間配置器線程安全問題補充 STL空間配置器那點事

摘要

在上一篇博客《STL空間配置器那點事》簡單介紹了空間配置器的基本實現html

兩級空間配置器處理,一級相關細節問題,同時簡單描述了STL各組件之間的關係以及設計到的設計模式等。node

在最後,又關於STL空間配置的效率以及空間釋放時機作了簡單的探討。linux

線程安全問題概述

爲何會有線程安全問題?ios

  認真學過操做系統的同窗應該都知道一個問題。git

  first--進程是系統資源分配和調度的基本單位,是操做系統結構的基礎,是一個程序的運行實體,同時也是一個程序執行中線程的容器github

  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

相關文章
相關標籤/搜索