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

来源:http://www.cnblogs.com/lang5230/archive/2016/06/12/5576336.html
-Advertisement-
Play Games

摘要 在上一篇博客《STL空間配置器那點事》簡單介紹了空間配置器的基本實現 兩級空間配置器處理,一級相關細節問題,同時簡單描述了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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 具體來說 cookie 是保存在“客戶端”的,而session是保存在“服務端”的 cookie 是通過擴展http協議實現的 cookie 主要包括 :名字,值,過期時間,路徑和域; 如果cookie不設置生命周期,則以瀏覽器關閉而關閉,這種cookie一般存儲在記憶體而不是硬碟上.若設置了生命周期 ...
  • 首先,將PHP項目的PHP版本設置為PHP 7.0。 PHP 7其中一項新特性是返回類型聲明,即PHP的函數和方法可以聲明指定類型的返回值: PHP 7的另一項精彩的改進就是參數的標量類型聲明,Netbeans的代碼自動完成功能提供了這些新類型支持。 NetBeans也支持PHP 7新出現的操作符: ...
  • 1、簡介 快速入門指南會對Yii2框架做一個基本介紹,包括資料庫遷移、gii操作、AR模型、路由、驗證、視圖等等。如果你是個Yii2新手甚至之前對PHP框架也很陌生,那麼這裡將會成為你的良好起點。如果你已經使用並且掌握了Yii2框架基礎,可以期待Yii2高級篇教程(後面我會更新)。 為了演示Yii2 ...
  • Python備份目錄及目錄下的全部內容 本來是想寫一個東西可以直接調用TortoiseSVN保存當前代碼到一個分枝下的。 可惜調用SVN的部分還在研究。就先寫了目錄拷貝的部分。 如果有喜歡研究Python的童鞋願意提供想法或者建議的話, 這裡先謝謝了。 :) 就目錄拷貝的部分,思想很簡單。讀配置文件 ...
  • 1、定義字元串 在PHP中,字元串的定義可以使用單引號,也可以使用雙引號。但是必須使用同一種單或雙引號來定義字元串,如:‘Hello"和“Hello'為非法的字元串定義。 定義字元串時,只有一種引號被視為定義符,即單引號或雙引號。於是,如果一個字元串由雙引號開始,那麼只有雙引號被分析器解析。這樣,你 ...
  • 1.Java數據類型 在介紹Java的自動裝箱和拆箱之前,我們先來瞭解一下Java的基本數據類型。 在Java中,數據類型可以分為兩大種,Primitive Type(基本類型)和Reference Type(引用類型)。基本類型的數值不是對象,不能調用對象的toString()、hashCode( ...
  • 本來想搞明白點wchar_t,就寫了個很簡單的wcout的例子,結果中文無法輸出,然後換成wprintf,卻還是不行~於是在網上搜啊搜,總算解決了問題,總結一下: 試著運行下麵的代碼: wchar_t *ws = L"你好"; wcout << ws << endl; 這樣沒有任何輸出,必須將loc ...
  • imagecreatetruecolor()建立的是一幅大小為 x和 y的黑色圖像(預設為黑色[即便叫法就是真彩色圖像]),如想改變背景顏色則需要用填充顏色函數 imagefill($img,0,0,$color); imagecreate 新建一個空白圖像資源,用imagecolorAlloca... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...