最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C++多線程編程時的數(shù)據(jù)保護

 更新時間:2015年07月14日 12:18:30   投稿:goldensun  
這篇文章主要介紹了C++多線程編程時的數(shù)據(jù)保護,作者針對C++11版本中的新特性做出了一些解說,需要的朋友可以參考下

 在編寫多線程程序時,多個線程同時訪問某個共享資源,會導致同步的問題,這篇文章中我們將介紹 C++11 多線程編程中的數(shù)據(jù)保護。
數(shù)據(jù)丟失

讓我們從一個簡單的例子開始,請看如下代碼:
 

#include <iostream>
#include <string>
#include <thread>
#include <vector>
 
using std::thread;
using std::vector;
using std::cout;
using std::endl;
 
class Incrementer
{
  private:
    int counter;
 
  public:
    Incrementer() : counter{0} { };
 
    void operator()()
    {
      for(int i = 0; i < 100000; i++)
      {
        this->counter++;
      }
    }
 
    int getCounter() const
    {
      return this->counter;
    }   
};
 
int main()
{
  // Create the threads which will each do some counting
  vector<thread> threads;
 
  Incrementer counter;
 
  threads.push_back(thread(std::ref(counter)));
  threads.push_back(thread(std::ref(counter)));
  threads.push_back(thread(std::ref(counter)));
 
  for(auto &t : threads)
  {
    t.join();
  }
 
  cout << counter.getCounter() << endl;
 
  return 0;
}

這個程序的目的就是數(shù)數(shù),數(shù)到30萬,某些傻叉程序員想要優(yōu)化數(shù)數(shù)的過程,因此創(chuàng)建了三個線程,使用一個共享變量 counter,每個線程負責給這個變量增加10萬計數(shù)。

這段代碼創(chuàng)建了一個名為 Incrementer 的類,該類包含一個私有變量 counter,其構造器非常簡單,只是將 counter 設置為 0.

緊接著是一個操作符重載,這意味著這個類的每個實例都是被當作一個簡單函數(shù)來調(diào)用的。一般我們調(diào)用類的某個方法時會這樣 object.fooMethod(),但現(xiàn)在你實際上是直接調(diào)用了對象,如object(). 因為我們是在操作符重載函數(shù)中將整個對象傳遞給了線程類。最后是一個 getCounter 方法,返回 counter 變量的值。

再下來是程序的入口函數(shù) main(),我們創(chuàng)建了三個線程,不過只創(chuàng)建了一個 Incrementer 類的實例,然后將這個實例傳遞給三個線程,注意這里使用了 std::ref ,這相當于是傳遞了實例的引用對象,而不是對象的拷貝。

現(xiàn)在讓我們來看看程序執(zhí)行的結果,如果這位傻叉程序員還夠聰明的話,他會使用 GCC 4.7 或者更新版本,或者是 Clang 3.1 來進行編譯,編譯方法:
 

g++ -std=c++11 -lpthread -o threading_example main.cpp

運行結果:

 

[lucas@lucas-desktop src]$ ./threading_example
218141
[lucas@lucas-desktop src]$ ./threading_example
208079
[lucas@lucas-desktop src]$ ./threading_example
100000
[lucas@lucas-desktop src]$ ./threading_example
202426
[lucas@lucas-desktop src]$ ./threading_example
172209

但等等,不對啊,程序并沒有數(shù)數(shù)到30萬,有一次居然只數(shù)到10萬,為什么會這樣呢?好吧,加1操作對應實際的處理器指令其實包括:
 

movl  counter(%rip), %eax
addl  $1, %eax
movl  %eax, counter(%rip)

首個指令將裝載 counter 的值到 %eax 寄存器,緊接著寄存器的值增1,然后將寄存器的值移給內(nèi)存中 counter 所在的地址。

我聽到你在嘀咕:這不錯,可為什么會導致數(shù)數(shù)錯誤的問題呢?嗯,還記得我們以前說過線程會共享處理器,因為只有單核。因此在某些點上,一個線程會依照指令執(zhí)行完成,但在很多情況下,操作系統(tǒng)會對線程說:時間結束了,到后面排隊再來,然后另外一個線程開始執(zhí)行,當下一個線程開始執(zhí)行時,它會從被暫停的那個位置開始執(zhí)行。所以你猜會發(fā)生什么事,當前線程正準備執(zhí)行寄存器加1操作時,系統(tǒng)把處理器交給另外一個線程?

我真的不知道會發(fā)生什么事,可能我們在準備加1時,另外一個線程進來了,重新將 counter 值加載到寄存器等多種情況的產(chǎn)生。誰也不知道到底發(fā)生了什么。

正確的做法

解決方案就是要求同一個時間內(nèi)只允許一個線程訪問共享變量。這個可通過 std::mutex 類來解決。當線程進入時,加鎖、執(zhí)行操作,然后釋放鎖。其他線程想要訪問這個共享資源必須等待鎖釋放。

互斥(mutex) 是操作系統(tǒng)確保鎖和解鎖操作是不可分割的。這意味著線程在對互斥量進行鎖和解鎖的操作是不會被中斷的。當線程對互斥量進行鎖或者解鎖時,該操作會在操作系統(tǒng)切換線程前完成。

而最好的事情是,當你試圖對互斥量進行加鎖操作時,其他的線程已經(jīng)鎖住了該互斥量,那你就必須等待直到其釋放。操作系統(tǒng)會跟蹤哪個線程正在等待哪個互斥量,被堵塞的線程會進入 "blocked onm" 狀態(tài),意味著操作系統(tǒng)不會給這個堵塞的線程任何處理器時間,直到互斥量解鎖,因此也不會浪費 CPU 的循環(huán)。如果有多個線程處于等待狀態(tài),哪個線程最先獲得資源取決于操作系統(tǒng)本身,一般像 Windows 和 Linux 系統(tǒng)使用的是 FIFO 策略,在實時操作系統(tǒng)中則是基于優(yōu)先級的。

現(xiàn)在讓我們對上面的代碼進行改進:
 

#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <mutex>
 
using std::thread;
using std::vector;
using std::cout;
using std::endl;
using std::mutex;
 
class Incrementer
{
  private:
    int counter;
    mutex m;
 
  public:
    Incrementer() : counter{0} { };
 
    void operator()()
    {
      for(int i = 0; i < 100000; i++)
      {
        this->m.lock();
        this->counter++;
        this->m.unlock();
      }
    }
 
    int getCounter() const
    {
      return this->counter;
    } 
};
 
int main()
{
  // Create the threads which will each do some counting
  vector<thread> threads;
 
  Incrementer counter;
 
  threads.push_back(thread(std::ref(counter)));
  threads.push_back(thread(std::ref(counter)));
  threads.push_back(thread(std::ref(counter)));
 
  for(auto &t : threads)
  {
    t.join();
  }
 
  cout << counter.getCounter() << endl;
 
  return 0;
}

注意代碼上的變化:我們引入了 mutex 頭文件,增加了一個 m 的成員,類型是 mutex,在operator()() 中我們鎖住互斥量 m 然后對 counter 進行加1操作,然后釋放互斥量。


再次執(zhí)行上述程序,結果如下:
 

[lucas@lucas-desktop src]$ ./threading_example
300000
[lucas@lucas-desktop src]$ ./threading_example
300000

這下數(shù)對了。不過在計算機科學中,沒有免費的午餐,使用互斥量會降低程序的性能,但這總比一個錯誤的程序要強吧。

防范異常

當對變量進行加1操作時,是可能會發(fā)生異常的,當然在我們這個例子中發(fā)生異常的機會微乎其微,但是在一些復雜系統(tǒng)中是極有可能的。上面的代碼并不是異常安全的,當異常發(fā)生時,程序已經(jīng)結束了,可是互斥量還是處于鎖的狀態(tài)。

為了確?;コ饬吭诋惓0l(fā)生的情況下也能被解鎖,我們需要使用如下代碼:
 

for(int i = 0; i < 100000; i++)
{
 this->m.lock();
 try
  {
   this->counter++;
   this->m.unlock();
  }
  catch(...)
  {
   this->m.unlock();
   throw;
  }
}

但是,這代碼太多了,而只是為了對互斥量進行加鎖和解鎖。沒關系,我知道你很懶,因此推薦個更簡單的單行代碼解決方法,就是使用 std::lock_guard 類。這個類在創(chuàng)建時就鎖定了 mutex 對象,然后在結束時釋放。

繼續(xù)修改代碼:
 

void operator()()
{
  for(int i = 0; i < 100000; i++)
  {
  lock_guard<mutex> lock(this->m);
 
  // The lock has been created now, and immediatly locks the mutex
  this->counter++;
 
  // This is the end of the for-loop scope, and the lock will be
  // destroyed, and in the destructor of the lock, it will
  // unlock the mutex
  }
}

上面代碼已然是異常安全了,因為當異常發(fā)生時,將會調(diào)用 lock 對象的析構函數(shù),然后自動進行互斥量的解鎖。

記住,請使用放下代碼模板來編寫:

 

void long_function()
{
  // some long code
 
  // Just a pair of curly braces
  {
  // Temp scope, create lock
  lock_guard<mutex> lock(this->m);
 
  // do some stuff
 
  // Close the scope, so the guard will unlock the mutex
  }
}

相關文章

  • C實現(xiàn)與 uint64_t 相同功能的類

    C實現(xiàn)與 uint64_t 相同功能的類

    本文給大家分享的是筆者實現(xiàn)的仿uint64_t的類,可以用在不支持uint64_t的平臺上,雖然現(xiàn)在功能還不完善,但是還是分享給大家,也算是給大家一個思路吧。
    2015-12-12
  • C++的字符串分割函數(shù)的使用詳解

    C++的字符串分割函數(shù)的使用詳解

    本篇文章主要介紹了C++的字符串分割函數(shù),主要用strtok、STL、Boost進行字符串分割,有需要的可以了解一下。
    2016-11-11
  • 簡單講解哈希表

    簡單講解哈希表

    本文主要介紹了哈希表簡單知識及C語言實現(xiàn)哈希表實例,文中利用圖片以及代碼簡單講解了相關知識,感興趣的小伙伴可以多多學習這篇文章
    2021-09-09
  • C++中順序表操作的示例代碼

    C++中順序表操作的示例代碼

    這篇文章主要為大家詳細介紹了C++中順序表的基礎操作的相關代碼,主要有順序表的輸出、插入和刪除數(shù)據(jù)等,感興趣的小伙伴可以了解一下
    2022-10-10
  • C++中左值和右值的區(qū)別詳解

    C++中左值和右值的區(qū)別詳解

    大家好,本篇文章主要講的是C++中左值和右值的區(qū)別詳解,感興趣的同學趕緊來看一看吧,對你有幫助的話記得收藏一下
    2022-01-01
  • C語言基礎 strlen 函數(shù)

    C語言基礎 strlen 函數(shù)

    這篇文章主要介紹了C語言基礎 strlen 函數(shù),在C 語言中,char 字符串也是一種非常重要的數(shù)據(jù)類型,我們可以使用 strlen 函數(shù)獲取字符串長度,這就是C語言strlen 函數(shù)的作用,下面我們來簡單介紹該內(nèi)容,需要的朋友可以參考以下
    2021-10-10
  • c++動態(tài)內(nèi)存空間示例(自定義空間類型大小和空間長度)

    c++動態(tài)內(nèi)存空間示例(自定義空間類型大小和空間長度)

    這篇文章主要介紹了c++動態(tài)內(nèi)存空間示例,自定義空間類型大小和空間長度,需要的朋友可以參考下
    2014-04-04
  • 淺談單調(diào)隊列、單調(diào)棧

    淺談單調(diào)隊列、單調(diào)棧

    其實,單調(diào)隊列和單調(diào)棧是類似的,在我看來,這兩個東西只是名字不一樣 - - ! 比較容易想的一道題啦! 首先,這題的兩個關鍵點: 1、區(qū)間的和。這個簡單,地球人都知道! 2、區(qū)間的最小值。
    2015-07-07
  • EasyC++編寫頭文件

    EasyC++編寫頭文件

    這篇文章主要介紹了C++編寫頭文件,在一個C++程序中,只包含兩類文件——.cpp文件和.h文件。其中,.cpp文件被稱作C++源文件,里面放的都是C++的源代碼;而.h文件則被稱作C++頭文件,里面放的也是C++的源代碼,感興趣的小伙伴一起來看下面文章的詳細介紹吧
    2021-12-12
  • C語言中實現(xiàn)KMP算法的實例講解

    C語言中實現(xiàn)KMP算法的實例講解

    KMP算法即字符串匹配算法,C語言中KMP可以避免指針回溯從而達到高效,接下來就來總結一下C語言中實現(xiàn)KMP算法的實例講解
    2016-06-06

最新評論

通江县| 永州市| 浦城县| 南宫市| 长泰县| 东乌珠穆沁旗| 鄂州市| 泾源县| 台南市| 嵩明县| 新郑市| 忻州市| 锦屏县| 诸城市| 文水县| 西充县| 教育| 宾阳县| 久治县| 安达市| 农安县| 茂名市| 花莲市| 津南区| 昔阳县| 集安市| 贵州省| 宜丰县| 安化县| 公安县| 伊春市| 平顺县| 宾川县| 灵山县| 山东省| 隆化县| 五大连池市| 蒙山县| 麻阳| 潮安县| 河北省|