1. 程式人生 > >C++多執行緒安全類的問題

C++多執行緒安全類的問題

我們是多麼渴望各種C++類都是多執行緒安全的,然而一旦涉及到物件間的互動,這樣的渴望可能就只能是奢望了。下面,我們以設計一個雙向鏈結點為例,看看要使其多執行緒安全將會帶來一些什麼問題。

class DoublyLinedNode{

DoublyLinedNode* pPrevNode_;

DoublyLinedNode* pNextNode_;

public:

DoublyLinedNode() : pPrevNode_(0), pNextNode_(0){}

virtual ~DoublyLinedNode();

public:

const DoublyLinedNode* GetPrevNode

() const{return pPrevNode_;}

const DoublyLinedNode* GetNextNode() const{return pNextNode_;}

public:

void InsertPrevNode(DoublyLinedNode* p);

void InsertNextNode(DoublyLinedNode* p);

void Break();

};

這是一個簡單的雙向鏈結點類,我們就討論討論其Break介面,這個介面的作用是使結點從其所在的鏈中斷開,如圖:

它的實現可能是這樣的:

void DoublyLinedNode::Break()

{

if (pPrevNode_)

{

pPrevNode_->pNextNode_ = pNextNode_;

}

if (pNextNode_)

{

pNextNode_->pPrevNode_ = pPrevNode_;

}

pPrevNode_ = 0;

pNextNode_ = 0;

}

這個實現是單執行緒模式的,沒有多執行緒安全性。

第一次嘗試:

void DoublyLinedNode::Break()

{

Lock();

if (pPrevNode_)

{

pPrevNode_->pNextNode_ = pNextNode_;

}

if (pNextNode_)

{

pNextNode_->pPrevNode_ = pPrevNode_;

}

pPrevNode_ = 0;

pNextNode_ = 0;

UnLock();

}

我們第一次嘗試將這個介面的程式碼用多執行緒鎖鎖住了,然而問題很明顯

if (pPrevNode_)

{

pPrevNode_->pNextNode_ = pNextNode_;

}

if (pNextNode_)

{

pNextNode_->pPrevNode_ = pPrevNode_;

}

我們這兩個對前向和後向結點的操作是修改另外兩個物件的內部狀態,多執行緒中,可能在此時正好有其他執行緒在對這兩個物件進行操作(訪問),或許程式就會因此而崩潰。

第二次嘗試:

void DoublyLinedNode::Break()

{

Lock();

if (pPrevNode_)

{

pPrevNode_->SetNextNode(pNextNode_); // SetNextNode同樣添加了鎖保護

}

if (pNextNode_)

{

pNextNode_->SetPrevNode(pPrevNode_); // SetPrevNode同樣添加了鎖保護

}

pPrevNode_ = 0;

pNextNode_ = 0;

UnLock();

}

這第二次嘗試將我們對前向和後繼結點的內部狀態的直接修改改成了對其介面的呼叫,我們試圖通過在其各種介面中加鎖來達到多執行緒安全的目的。然而這卻引入了新的問題,我們在一個被鎖住的程式碼中進行了又呼叫了另外會使用鎖的程式碼,這最可能引發的問題就是資源競爭,而在我們這次嘗試中引如的問題的確就是資源競爭,導致死鎖

我們在不同執行緒中對結點1和結點2同時呼叫Break,當1申請到自身的鎖之後,準備呼叫2的介面,此時2也申請到了自身的鎖,準備呼叫1的介面。由於1已經佔有了自身的鎖,2也佔有了自身的鎖,那麼1將會在呼叫2的介面的地方等待2的鎖,而2將會在呼叫1的介面的地方等待1 12的相互等待就形成了死鎖

第三次嘗試:

void DoublyLinedNode::Break()

{

Lock();

if (pPrevNode_)

{

pPrevNode_-> Lock();

pPrevNode_->SetNextNode(pNextNode_);

pPrevNode_-> UnLock ();

}

if (pNextNode_)

{

pNextNode_-> Lock();

pNextNode_->SetPrevNode(pPrevNode_);

pNextNode_-> UnLock ();

}

pPrevNode_ = 0;

pNextNode_ = 0;

UnLock();

}

這次嘗試顯得比較愚蠢,將外部物件加鎖的過程提到了自身Break當中效果和第二次嘗試是一樣的,沒有得到任何的改善。

第四次嘗試:

void DoublyLinedNode::Break()

{

SharedLock();

if (pPrevNode_)

{

pPrevNode_->SetNextNode(pNextNode_);

}

if (pNextNode_)

{

pNextNode_->SetPrevNode(pPrevNode_);

}

pPrevNode_ = 0;

pNextNode_ = 0;

SharedUnLock();

}

這次嘗試取得了一定的成功,對於這些關係密切,存在相互呼叫的物件,我們使用了共享鎖,它的確將我們的多執行緒訪問衝突和死鎖問題解決了,但是這個共享鎖的實現難度是相當大的,你必須要保證可能產生相互呼叫的物件都要進行鎖共享,那麼你對於增加、修改、刪除物件這些管理工作將會變得極度困難,稍有差池就會引發問題,而且別人在使用你的類的時候也同樣需要處處小心,這不是我們所期望的。

以上我們進行了四次嘗試將我們的雙向鏈結點類設計成多執行緒安全,顯然我們已經筋疲力盡,卻未能達到滿意的效果。

在這裡我建議大家設計這種類的時候儘量設計成單執行緒模式,在框架設計中去考慮多執行緒問題,比如使用單執行緒訪問物件,而模組間使用非同步通訊來進行互動等。

多執行緒程式設計的確非常困難,C++在這方面又表現得力不從心,我在這裡引入這個問題旨在於告誡大家在對待多執行緒問題上一定要細心細心再細心。

相關推薦

C++執行安全的問題

我們是多麼渴望各種C++類都是多執行緒安全的,然而一旦涉及到物件間的互動,這樣的渴望可能就只能是奢望了。下面,我們以設計一個雙向鏈結點為例,看看要使其多執行緒安全將會帶來一些什麼問題。 class DoublyLinedNode{ DoublyLinedNode* pPrev

C#執行 BackgroundWorker使用小例-WPF程式

1.程式實現了一個簡單的使用了BackgroundWorker類的WPF程式,用於在後臺執行緒給進度條賦值。 執行結果如下: 後臺執行緒正常執行結束: 後臺執行緒中途被取消: 2.程式僅修改了 MainWindow.xaml 檔案和 MainWindow.xaml.

c/c++ 執行 利用條件變數實現執行安全的佇列

多執行緒 利用條件變數實現執行緒安全的佇列 背景:標準STL庫的佇列queue是執行緒不安全的。 利用條件變數(Condition variable)簡單實現一個執行緒安全的佇列。 程式碼: #include <queue> #include <memory> #include

c# 執行使用佇列順序寫日誌的 (需要再優化)

using System; using System.Collections.Generic; using System.Threading; public class LogManager { /// <summary> /// 建構函式 /// </su

C# 利用執行安全資料結構BlockingCollection實現執行

using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using Danny.Infrastructure.Helper; names

C# 執行之List的執行安全問題

網上關於List的執行緒安全問題將的很少,所以自己實驗了一把,發現確實是執行緒不安全的.所以當你在進行多執行緒程式設計中使用了共享的List集合,必須對其進行執行緒安全處理. List的Add方法是執行緒不安全的,List的原始碼中的Add方法,使用了每次噹噹前的元素達到上限,通過建立一個新的陣列例項,並給

簡單的Linux C++執行CLOCK(時鐘)

剛剛加入CSDN部落格,初來乍到也不知道寫什麼,所以來分享一個自己以前學C++的時候寫的第一個類,一個關於時鐘的簡單的Linux多執行緒CLOCK(時鐘)類: /***********************************************

C# 執行九之Timer

1、簡介 相信寫過定時任務的小夥伴都知道這個類,非常的輕量級,而且FCL中大量的類使用了這個方法,比如CancellationTokenSource的CancelAfter就是用Timer去做的. 當然FCL中大量的使用了Timer,說明MS對Timer類是信任的.下面就開始介紹這個類的用法.簡介很少

C# 執行呼叫靜態方法或者靜態例項中的同一個方法-方法內部的變數是執行安全

 C#  多執行緒呼叫靜態方法或者靜態例項中的同一個方法-方法內部的變數是執行緒安全的       using System;using System.Threading;using System.Threading.Tasks;using Sys

執行 繼承Thread 實現Runnable介面 執行安全 synchronized 單例設計懶漢 鎖死

程序 是一個正在執行中的程式。 每個程序執行都有一個執行順序,該順序是一個執行路徑,或者叫一個控制單元。 舉例子:一個程序,就是一個正在執行的程式。 執行緒 就是程序中的一個獨立的控制單元。 執行緒在

Win32下兩種用於C++的執行同步(執行實現加鎖解鎖)

使用Win32提供的臨界區可以方便的實現執行緒鎖: // 全域性: CRITICAL_SECTION cs; InitializeCriticalSection( & cs); // 執行緒1: EnterCriticalSection(

ThreadLocal:解決執行安全訪問 靜態變數 的問題

JAVA 多執行緒程式設計 安全結論: 靜態變數:執行緒非安全。 靜態變數即類變數,位於方法區,為所有物件共享,共享一份記憶體,一旦靜態變數被修改,其他物件均對修改可見,故執行緒非安全。 例項變數:單例模式(只有一個物件例項存在)執行緒非安全,非單例執行緒安全。 例項變

C#執行程式設計筆記(2.5)-使用CountDownEvent

近來在學習Eugene Agafonov編寫的《C#多執行緒程式設計實戰》(譯),做些筆記也順便分享一下^-^using System; using System.Threading; namespace CountDownEvent_Test { class Pr

C#執行程式設計筆記(2.7)-使用ReaderWriterLockSlim

近來在學習Eugene Agafonov編寫的《C#多執行緒程式設計實戰》(譯),做些筆記也順便分享一下^-^using System; using System.Collections.Generic; using System.Threading; namespace

C#執行程式設計筆記(2.1)-使用Mutex

近來在學習Eugene Agafonov編寫的《C#多執行緒程式設計實戰》(譯),做些筆記也順便分享一下^-^Mutex是一種原始的同步方法,其只對一個執行緒授予對共享資源的獨佔訪問using System; using System.Threading; namespac

C#執行開發10:執行同步之Semaphore

Semaphore類表示訊號量。 訊號量和互斥類似,只是訊號量可以同時由多個執行緒使用,而互斥只能由一個執行緒使用。也就是說,使用訊號量時,可以多個執行緒同時訪問受保護的資源。下面例項演示了“學生到食

執行】初步瞭解java執行安全的容器CopyOnWriteArrayList

通常我們理解上,執行緒安全的容器類一般指Vector、HashTable等,但在進一步瞭解後,其實真正意義上的執行緒安全沒有那麼簡單。 執行緒安全實際上分為多個級別: (1)不可變 不可變類,典型例子是常用的String、Integer、Long等,作為不可變類,任何一

C++執行程式設計(執行)

簡述 通過執行緒類來管理執行緒,實現業務邏輯與執行緒管理分離 原始碼 介面類 SFRunnable.h class SFRunnable { public: virtual ~SFRunnable() {}; vi

C#執行程式設計筆記(2.6)-使用Barrier

近來在學習Eugene Agafonov編寫的《C#多執行緒程式設計實戰》(譯),做些筆記也順便分享一下^-^using System; using System.Threading; namespace Barrier_Test { class Program

C# 執行計時器

該例子是一個封裝的計時器類。 實現功能: (1)傳入一個時間 HH:mm:ss 字串 和 label控制元件物件 即可實現控制元件倒計時。 三行程式碼即可執行,如下圖效果: 當然,每個專案需要的需求不同,這裡只是拋磚引玉。 下面貼出我的程式碼,歡迎討論。 完整程式碼