1. 程式人生 > >C++多執行緒同步之Mutex(互斥量)

C++多執行緒同步之Mutex(互斥量)

一、互斥量Mutex同步多執行緒

1、Win32平臺

相關函式和標頭檔案

#include <windows.h>
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTESlpMutexAttributes, // 指向安全屬性的指標
BOOLbInitialOwner, // 初始化互斥物件的所有者
LPCTSTRlpName // 指向互斥物件名的指標
);

DWORD WINAPI WaitForSingleObject(
__in HANDLE hHandle,//互斥量物件控制代碼
__in DWORD dwMilliseconds//等待時間
);

BOOL WINAPI ReleaseMutex(HANDLE
hMutex); 返回值:BOOL,TRUE表示成功,FALSE表示失敗。 引數表:hMutex:HANDLE,制定一個互斥體的控制代碼。 BOOL CloseHandle(HANDLE hObject); 引數: hObject 代表一個已開啟物件handle。 返回值: TRUE:執行成功; FALSE:執行失敗,可以呼叫GetLastError()獲知失敗原因。

原始碼:
從本篇開始,我對程式碼會進行一些封裝,使之更貼近實際使用的情況。

/***MyMutex.h標頭檔案***/

#ifndef __MY_MUTEX_H
#define __MY_MUTEX_H
#include <windows.h>

class CMyMutex
{
public
: CMyMutex(); virtual ~CMyMutex(); void Lock(); void UnLock(); private: HANDLE m_hMutex; }; class CAutoLock { public: CAutoLock(CMyMutex* pMutex); virtual ~CAutoLock(); private: CMyMutex* m_pMutex; }; #endif;
/***MyMutex.cpp檔案***/

#include <iostream>
#include <windows.h> #include "MyMutex.h" using namespace std; CMyMutex::CMyMutex() { m_hMutex = CreateMutex(NULL /*預設安全屬性*/ , false /*建立執行緒不擁有該訊號量*/ , NULL /*鎖名稱*/ ); } CMyMutex::~CMyMutex() { if(NULL != m_hMutex) { CloseHandle(m_hMutex); cout<<"m_hMutex被關閉"<<endl; } } void CMyMutex::Lock() { if(NULL == m_hMutex) { cout<<"m_hMutex為空"<<endl; return; } DWORD dRes = -1; dRes = WaitForSingleObject(m_hMutex, INFINITE); if(WAIT_OBJECT_0 == dRes) { // cout<<"上鎖成功!"<<endl; } else if(WAIT_ABANDONED == dRes) { cout<<"發生鎖死現象"<<endl; } else if(WAIT_TIMEOUT == dRes) { cout<<"等待超時"<<endl; } else if(WAIT_FAILED == dRes) { cout<<"發生錯誤"<<endl; } else { cout<<"上鎖失敗!"<<endl; } } void CMyMutex::UnLock() { ReleaseMutex(m_hMutex); } //****************************CAutoLock***************************************** CAutoLock::CAutoLock(CMyMutex* pMutex) { m_pMutex = pMutex; m_pMutex->Lock(); } CAutoLock::~CAutoLock() { m_pMutex->UnLock(); }
/***main.cpp檔案***/

#include <iostream>
#include <windows.h>
#include "MySemaphore.h"
#include "MyMutex.h"
using namespace std;

CMyMutex        MyMutex;/*宣告一個全域性的互斥量物件(自己封裝的)*/

DWORD WINAPI Fun(LPVOID lpParamter)
{
    string strPrint((const char*)lpParamter);
    int iRunTime = 0;
    //執行100次跳出
    while(++iRunTime<100)
    {
        /*利用CMyMutex的建構函式和解構函式分別取建立和關閉互斥量
          利用CAutoLock的構造和解構函式去WaitForSingleObject和ReleaseMutex互斥量
        */
        CAutoLock cLock(&MyMutex);
        cout <<"["<< iRunTime <<"]:"<< strPrint.c_str()<<endl;
        //執行緒函式阻塞,交出CPU使用許可權
        Sleep(10);
    }
    return 0;
}

int main()
{
    //建立子執行緒
    string str1 = "A";
    string str2 = "B";
    string str3 = "C";
    string str4 = "D";
    string str5 = "E";

    HANDLE hThread1 = CreateThread(NULL, 0, Fun, (void*)str1.c_str(), 0, NULL);
    HANDLE hThread2 = CreateThread(NULL, 0, Fun, (void*)str2.c_str(), 0, NULL);
    HANDLE hThread3 = CreateThread(NULL, 0, Fun, (void*)str3.c_str(), 0, NULL);
    HANDLE hThread4 = CreateThread(NULL, 0, Fun, (void*)str4.c_str(), 0, NULL);
    HANDLE hThread5 = CreateThread(NULL, 0, Fun, (void*)str5.c_str(), 0, NULL);

    //關閉執行緒
    CloseHandle(hThread1);
    CloseHandle(hThread2);
    CloseHandle(hThread3);
    CloseHandle(hThread4);
    CloseHandle(hThread5);

    getchar();
//  system("pause");
    return 0;
}

執行結果:這裡寫圖片描述
五個執行緒分別列印字串A到E,各執行99次,沒有出現列印混亂(對螢幕資源進行爭奪)的情況。

另外有興趣的讀者可以把程式碼敲一遍,每個執行緒列印9次,然後把CAutoLock的解構函式內的 m_pMutex->UnLock();註釋起來會出現什麼情況?可以思考一下。
執行結果:這裡寫圖片描述
出現的現象是:每個執行緒列印了9次就出現了“發生死鎖現象”,而且列印A的執行緒居然可以不停的對m_pMutex->Lock();這是為什麼呢?
WAIT_ABANDONED 0x00000080:當hHandle為mutex時,如果擁有mutex的執行緒在結束時沒有釋放核心物件會引發此返回值。這就是為什麼會列印“發生死鎖現象”,可能這裡的提示寫的不是很恰當。
另外可以重複執行m_pMutex->Lock();是因為列印A執行緒從最開始已經WaitForSingleObject到該互斥量,並且處於有訊號狀態,因此該執行緒可以一直列印,列印9次之後,執行緒已經關閉(實際上執行緒在列印完9次之前已經被CloseHandle()了),因此才會出現返回WAIT_ABANDONED 。
在這裡為什麼列印D執行緒又能WaitForSingleObject,使互斥量變為有訊號狀態,那可能就需要知道系統會對未釋放核心物件互斥量進行什麼處理。從執行結果看,系統又把它變為有訊號狀態,讓其他執行緒可用了。

2、Linux平臺

相關標頭檔案和API

#include<pthread.h>
#include<errno.h>
//初始化訊號量介面,如果使用預設的屬性初始化互斥量, 只需把attr設為NULL.
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restric attr);
//銷燬訊號量物件介面
int pthread_mutex_destroy(pthread_mutex_t *mutex);
//互斥量加鎖介面--阻塞式
//說明:對共享資源的訪問, 要對互斥量進行加鎖, 如果互斥量已經上了鎖, 呼叫執行緒會阻塞, 直到互斥量被解鎖。在完成了對共享資源的訪問後, 要對互斥量進行解鎖。
int pthread_mutex_lock(pthread_mutex_t *mutex);

//互斥量加鎖介面--非阻塞式
//說明: 這個函式是非阻塞呼叫模式, 也就是說, 如果互斥量沒被鎖住, trylock函式將把互斥量加鎖, 並獲得對共享資源的訪問許可權; 如果互斥量被鎖住了, trylock函式將不會阻塞等待而直接返回EBUSY,表示共享資源處於忙狀態。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
//互斥量解鎖介面
int pthread_mutex_unlock(pthread_mutex_t *mutex);

//上述所有返回值: 成功則返回0, 出錯則返回錯誤編號。

初始化:
在Linux下, 執行緒的互斥量資料型別是pthread_mutex_t. 在使用前, 要對它進行初始化:
對於靜態分配的互斥量,可以把它設定為PTHREAD_MUTEX_INITIALIZER,或者呼叫pthread_mutex_init;
對於動態分配的互斥量, 在申請記憶體(malloc)之後, 通過pthread_mutex_init進行初始化,並且在釋放記憶體(free)前需要呼叫pthread_mutex_destroy;

死鎖:
死鎖主要發生在有多個依賴鎖存在時, 會在一個執行緒試圖以與另一個執行緒相反順序鎖住互斥量時發生。如何避免死鎖是使用互斥量應該格外注意的東西。

總體來講, 有幾個不成文的基本原則:

  • 對共享資源操作前一定要獲得鎖。
  • 完成操作以後一定要釋放鎖。
  • 儘量短時間地佔用鎖。
  • 如果有多鎖, 如獲得順序是ABC連環扣, 釋放順序也應該是ABC。
  • 執行緒錯誤返回時應該釋放它所獲得的鎖。

各種Mutex的區別:

鎖型別 初始化方式 加鎖特徵 排程特徵
普通鎖 PTHREAD_MUTEX_INITIALIZER 同一執行緒可重複加鎖,解鎖一次釋放鎖 先等待鎖的程序先獲得鎖
巢狀鎖 PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP 同一執行緒可重複加鎖,解鎖同樣次數才可釋放鎖 先等待鎖的程序先獲得鎖
糾錯鎖 PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP 同一執行緒不能重複加鎖,加上的鎖只能由本執行緒解鎖 先等待鎖的程序先獲得鎖
自適應鎖 PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP 同一執行緒可重加鎖,解鎖一次生效 所有等待鎖的執行緒自由競爭

程式碼:

/********************************Copyright Qinlong*****************************
** File   Name:  Mutex.cpp 
** Create Date:  2016.11.15
** Modify Time:  2016.11.16
** Function:     mutex synchornization
** Author:       qin long
** Modifier:     **
** Version:      1.0
*******************************************************************************/

#include <iostream>
#include <pthread.h>
#include <errno.h>
using namespace std;

//普通鎖
static pthread_mutex_t g_mutex=PTHREAD_MUTEX_INITIALIZER;
//迴圈執行次數
static const int g_iRunTime = 100;

void* Fun(void* ptr)
{
   int iRunTime = 0;
   while(++iRunTime< g_iRunTime)
   {
      pthread_mutex_lock(&g_mutex);
      cout << iRunTime << ": Fun() is running!" << endl;
//    若下面一行程式碼不註釋,則主函式輸出會出現列印"main trylock failed!",
//    原因就在於g_mutex鎖被本執行緒函式長期佔用的結果.
//    usleep(200);  
      pthread_mutex_unlock(&g_mutex);
      usleep(100000);
    }
}


int main()
{
   pthread_t hHandle;
   int iRet = pthread_create(&hHandle, NULL, Fun, NULL);    //create a thread;
   if(0 != iRet)
   {
       cout << "Create thread failed!" << endl;
   }
   sleep(1); 
   int iRunTime = 0;
   while(++iRunTime<g_iRunTime)
   {
        //這裡僅僅是為了測試pthread_mutex_trylock的用法
      if(EBUSY==pthread_mutex_trylock(&g_mutex))
      {
           cout<< "main trylock failed!"<<endl;
           --iRunTime;
      }
      else
      {
           cout <<iRunTime<< ": main is running!" << endl;
           pthread_mutex_unlock(&g_mutex);
           usleep(100000);
      }
   }
   pthread_join(hHandle, NULL);
   return 0;
} 

執行結果:
註釋掉Fun中uSleep(200);的結果如下圖所示,
這裡寫圖片描述

未註釋掉Fun中uSleep(200);的結果如下圖所示,
這裡寫圖片描述
這裡執行結果出現了main trylock failed!原因是由於Fun函式在列印輸出完畢後使用uSleep(200)“長時間佔用”鎖導致的,從使用pthread_mutex_trylock我們可以看到主函式在經過多次嘗試進行加鎖都失敗了。因此我們的設計原則應該就是儘可能短時間去佔用鎖,才能提高多執行緒之間的執行以及同步效率。

相關推薦

C++執行同步Mutex(互斥)

一、互斥量Mutex同步多執行緒 1、Win32平臺 相關函式和標頭檔案 #include <windows.h> HANDLE CreateMutex( LPSECURITY_ATTRIBUTESlpMutexAttributes

C++執行-第二篇-Mutex(互斥)

//Boost#include<boost/thread/thread.hpp>#define BOOST_THREAD_VERSION 4 //使用最新版本,含有1,2,3但只是為了相容之前程式。 Thread庫豐富強大的擴充套件功能但不在Thre

C++執行同步Semaphore(訊號)

一、執行緒間同步的幾種方式 從上篇博文中可以發現,當多個執行緒對同一資源進行使用時,會產生“爭奪”的情況,為了避免這種情況的產生,也就出現了執行緒間的同步這個技術。執行緒間的同步有多種方式,在接下來的博文中我會依次介紹幾種主流的同步方式,以及他們之間的區別。在

C++執行同步事件(Event)

一、事件(Event)原理解析 1、執行緒同步Event,主要用於執行緒間的等待通知。 2、核心物件中,事件核心物件是個最基本的物件。 3、事件包含一個使用計數(與所有核心物件一樣),一個用於指明該事件是個自動重置的事件還是人工重置的事件的布林值,另一個用

C++執行同步效率對比臨界區和原子鎖

多執行緒程式設計經常遇到資料同步問題,通常的做法就是加鎖,之前個人常用臨界區(CTITICAL_SECTION),最近開發高效率程式,尋求更高效的同步方式,才有了對原子鎖的研究。經測試,原子鎖效率確實比臨界區高,用資料衡量,原子鎖的效率是臨界區的5倍左右。 測試方法: 1、

【VS2010】C++執行同步互斥簡單運用

繼以往的想法,寫這點文字,貼上點程式碼,是為了增加自己的記憶,也希望能幫助到需要幫助的人。 1.  互斥量,Mutex #include <Windows.h> #include <iostream> usingnamespace

C# 執行(lock,Monitor,Mutex,同步事件和等待控制代碼)

原文地址:http://www.cnblogs.com/SkySoot/archive/2012/04/02/2430295.html 本篇從 Monitor,Mutex,ManualResetEvent,AutoResetEvent,WaitHandler 的類關係圖

C++執行例項臨界區同步

本篇是對上一篇 進行了重構,增加了windos下的臨界區鎖。 臨界區的特點:非核心物件,只能在window下使用,linux下不能使用;只能在同一程序內的執行緒間使用,速度快。 互斥量特點:互斥量是核心物件,可以用於程序內也可以在程序間互斥,速度相對互斥量慢點,也可以

C# 執行Task(任務)

1、簡介 為什麼MS要推出Task,而不推Thread和ThreadPool,以下是我的見解: (1)、Thread的Api並不靠譜,甚至MS自己都不推薦,原因,它將整個Thread類都不開放給Windows Sotre程式,且它的Api過於強大,如果在程式中過度使用,維護的成本太高,想想程式碼中充斥著掛

C# 執行Task(任務)二

前面介紹了Task的由來,以及簡單的使用,包括開啟任務,處理任務的超時、異常、取消、以及如果獲取任務的返回值,在回去返回值之後,立即喚起新的執行緒處理返回值、且如果前面的任務發生異常,喚起任務如果有效的處理異常等關於Task的知識。所以本文將介紹Task更多的用法和特性.   一、如果通過一個任

C# 執行Task(任務)三任務工廠 C# 執行Task(任務)二

1、知識回顧,簡要概述 前面兩篇關於Task的隨筆,C# 多執行緒五之Task(任務)一 和 C# 多執行緒六之Task(任務)二,介紹了關於Task的一些基本的用法,以及一些使用的要點,如果都看懂了,本文將介紹另一個Task的特殊用法,前面介紹了,如何通過一個父任務建立多個子任務,且這

C# 執行Parallel

1、簡介 關於Parallel不想說太多,因為它是Task的語法糖,至少我是這麼理解的,官方文件也是這麼說的,它本身就是基本Task的.假設我們有一個集合,不管是什麼集合,我們要遍歷它,首先想到的是For(如何涉及到修改或者讀可以用for)或者Foreach(如果單純的讀),但是它兩是同步的去操作集合,但是

執行同步——兩個執行序列順序列印奇數和偶數的兩種實現

題目:一道經典的執行緒併發的問題,執行緒a列印1、3、5……,執行緒b列印2、4、6……,兩個執行緒交替執行輸出1、2、3、4、5、6…… 要點: package com.test; import java.util.concurrent.locks.

C++執行同步技巧(二) ---事件

簡介 Windows線上程控制方面提供了多種訊號處理機制,其中一種便是使用 CreateEvent() 函式建立事件,然後使用訊號控制執行緒執行。其中將事件變為有訊號可使用 SetEvent() 函式,將事件訊號復位(變為無訊號)可使用 ResetEvent(

C# 執行Timer類

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

Linux 執行同步哲學家用餐問題

問題描述: 有五個哲學家公用一張餐桌,分別坐在周圍的五張椅子上,在餐桌上有五個碗和五隻筷子,他們的生活方式是交替地進行思考和用餐。平時,一個哲學家進行思考,飢餓時便試圖拿取其左右最靠近他的筷子,只有在他拿到兩隻筷子時才能進餐,進餐完畢,放下筷子繼續思考。(計算機作業系統 第

[Linux]執行同步sem_wait()學習筆記

2、用Condition Variable實現Semaphore ,即用pthread_cond_wait()等方式實現sem_wait()等方式。 #include <stdlib.h> #include <pthread.h> #include <stdio.h>

Linux 執行同步訊息佇列

訊息佇列是訊息的連結串列,存放在核心中並有訊息佇列標示符標示。   msgget用於建立一個新佇列或開啟一個現存的佇列。msgsnd將新訊息加入到訊息佇列中;每個訊息包括一個long型的type;和訊息快取;msgrcv用於從佇列中取出訊息;取訊息很智慧,不一定先進先出   ①msgget,建立一個新

C#執行同步(一)【計數器】

羅馬人凱撒大帝,威震歐亞非三大陸,臨終告訴侍者說:“請把我的雙手放在棺材外面,讓世人看看,偉大如我凱撒者,死後也是兩手空空。 ICounter.cs類: interface ICounter

執行同步事件 -- 2個執行交替列印數字

    有段時間沒有接觸多執行緒相關的知識了,難免會遺忘或者生疏。多執行緒通訊和同步相關的知識運用比較廣,而且比較常見。今天通過2個執行緒交替列印數字的例子,來整理下多執行緒序相關的程式設計方式。這裡2個執行緒直接通過一個event進行同步。     我們通過CreateE