程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> C++多線程編程時的數據掩護

C++多線程編程時的數據掩護

編輯:關於C++

C++多線程編程時的數據掩護。本站提示廣大學習愛好者:(C++多線程編程時的數據掩護)文章只能為提供參考,不一定能成為您想要的結果。以下是C++多線程編程時的數據掩護正文


 在編寫多線程法式時,多個線程同時拜訪某個同享資本,會招致同步的成績,這篇文章中我們將引見 C++11 多線程編程中的數據掩護。
數據喪失

讓我們從一個簡略的例子開端,請看以下代碼:
 

#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;
}

這個法式的目標就是數數,數到30萬,某些傻叉法式員想要優化數數的進程,是以創立了三個線程,應用一個同享變量 counter,每一個線程擔任給這個變量增長10萬計數。

這段代碼創立了一個名為 Incrementer 的類,該類包括一個公有變量 counter,其結構器異常簡略,只是將 counter 設置為 0.

緊接著是一個操作符重載,這意味著這個類的每一個實例都是被看成一個簡略函數來挪用的。普通我們挪用類的某個辦法時會如許 object.fooMethod(),但如今你現實上是直接挪用了對象,如object(). 由於我們是在操作符重載函數中將全部對象傳遞給了線程類。最初是一個 getCounter 辦法,前往 counter 變量的值。

再上去是法式的進口函數 main(),我們創立了三個線程,不外只創立了一個 Incrementer 類的實例,然後將這個實例傳遞給三個線程,留意這裡應用了 std::ref ,這相當因而傳遞了實例的援用對象,而不是對象的拷貝。

如今讓我們來看看法式履行的成果,假如這位傻叉法式員還夠聰慧的話,他會應用 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

但等等,纰謬啊,法式並沒稀有數到30萬,有一次竟然只數到10萬,為何會如許呢?好吧,加1操尴尬刁難應現實的處置器指令其實包含:
 

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

首個指令將裝載 counter 的值到 %eax 存放器,緊接著存放器的值增1,然後將存放器的值移給內存中 counter 地點的地址。

我聽到你在嘀咕:這不錯,可為何會招致數數毛病的成績呢?嗯,還記得我們之前說過線程會同享處置器,由於只要單核。是以在某些點上,一個線程會按照指令履行完成,但在許多情形下,操作體系會對線程說:時光停止了,到前面列隊再來,然後別的一個線程開端履行,當下一個線程開端履行時,它會從被暫停的誰人地位開端履行。所以你猜會產生甚麼事,以後線程正預備履行存放器加1操作時,體系把處置器交給別的一個線程?

我真的不曉得會產生甚麼事,能夠我們在預備加1時,別的一個線程出去了,從新將 counter 值加載到存放器等多種情形的發生。誰也不曉得究竟產生了甚麼。

准確的做法

處理計劃就是請求統一個時光內只許可一個線程拜訪同享變量。這個可經由過程 std::mutex 類來處理。當線程進入時,加鎖、履行操作,然後釋放鎖。其他線程想要拜訪這個同享資本必需期待鎖釋放。

互斥(mutex) 是操作體系確保鎖息爭鎖操作是弗成朋分的。這意味著線程在對互斥量停止鎖息爭鎖的操作是不會被中止的。當線程對互斥量停止鎖或許解鎖時,該操作會在操作體系切換線程前完成。

而最好的工作是,當你試圖對互斥量停止加鎖操作時,其他的線程曾經鎖住了該互斥量,那你就必需期待直到其釋放。操作體系會跟蹤哪一個線程正在期待哪一個互斥量,被梗塞的線程會進入 "blocked onm" 狀況,意味著操作體系不會給這個梗塞的線程任何處置器時光,直到互斥量解鎖,是以也不會糟蹋 CPU 的輪回。假如有多個線程處於期待狀況,哪一個線程最早取得資本取決於操作體系自己,普通像 Windows 和 Linux 體系應用的是 FIFO 戰略,在及時操作體系中則是基於優先級的。

如今讓我們對下面的代碼停止改良:
 

#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操作,然後釋放互斥量。


再次履行上述法式,成果以下:
 

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

這下數對了。不外在盤算機迷信中,沒有收費的午飯,應用互斥量會下降法式的機能,但這總比一個毛病的法式要強吧。

防備異常

當對變量停止加1操作時,是能夠會產生異常的,固然在我們這個例子中產生異常的機遇微不足道,然則在一些龐雜體系中是極有能夠的。下面的代碼其實不是異常平安的,當異常產生時,法式曾經停止了,可是互斥量照樣處於鎖的狀況。

為了確保互斥量在異常產生的情形下也能被解鎖,我們須要應用以下代碼:
 

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

然則,這代碼太多了,而只是為了對互斥量停止加鎖息爭鎖。沒緊要,我曉得你很懶,是以推舉個更簡略的單行代碼處理辦法,就是應用 std::lock_guard 類。這個類在創立時就鎖定了 mutex 對象,然後在停止時釋放。

持續修正代碼:
 

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
  }
}

下面代碼已然是異常平安了,由於當異常產生時,將會挪用 lock 對象的析構函數,然後主動停止互斥量的解鎖。

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

 

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
  }
}

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