程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> stl空間配置器線程安全問題補充,stl安全問題

stl空間配置器線程安全問題補充,stl安全問題

編輯:C++入門知識

stl空間配置器線程安全問題補充,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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved