1. 程式人生 > >執行緒的同步與互斥:互斥鎖

執行緒的同步與互斥:互斥鎖

什麼是執行緒的同步與互斥?

  • 互斥:指在某一時刻指允許一個程序執行其中的程式片,具有排他性和唯一性。
    對於執行緒A和執行緒B來講,在同一時刻,只允許一個執行緒對臨界資源進行操作,即當A進入臨界區對資源操作時,B就必須等待;當A執行完,退出臨界區後,B才能對臨界資源進行操作。
  • 同步:指的是在互斥的基礎上,實現程序之間的有序訪問。假設現有執行緒A和執行緒B,執行緒A需要往緩衝區寫資料,執行緒B需要從緩衝區讀資料,但他們之間存在一種制約關係,即當執行緒A寫的時候,B不能來拿資料;B在拿資料的時候A不能往緩衝區寫,也就是說,只有當A寫完資料(或B取走資料),B才能來讀資料(或A才能往裡寫資料)。這種關係就是一種執行緒的同步關係。

那什麼是臨界資源和臨界區呢?

  • 臨界資源:能夠被多個執行緒共享的資料/資源。
  • 臨界區:對臨界資源進行操作的那一段程式碼

多執行緒程式設計中,難免會遇到多個執行緒同時訪問臨界資源的問題,如果不對其加以保護,那麼結果肯定是不如預期的。看下面這段程式碼:

static int g_val=0;
void* pthread_mem(void* arg)
{
    int i=0;
    int val=0;
    while(i<500000)
    {
        val = g_val;
        i++;
        g_val=val+1;
    }
    return
NULL; } int main() { pthread_t tid1; pthread_t tid2; pthread_create(&tid1, NULL, pthread_mem, NULL); pthread_create(&tid2, NULL, pthread_mem, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); printf("g_val end is :%d\n",g_val); return 0; }

執行緒1和執行緒2都需要對g_val進行+1操作,迴圈500000次,由於每次對g_val進行+1操作時並不是一部完成的(原子操作),在一個執行緒執行過程中隨時都有可能被切出去使另一個執行緒來操作,假設執行緒1正在執行時被切出去,此時它已經將g_val累加到3000,而執行緒2切進來的時候並不知情,可能會將g_val從0開始累加。我們期望的是執行緒1和執行緒2能將g_val累加到100 0000,但實際結果確實這樣的:
這裡寫圖片描述


很明顯,結果是一個隨機數。。。

執行緒同步與互斥的實現

  • 互斥鎖(Mutex)
    1、互斥鎖的本質:
    首先需要明確一點,互斥鎖實際上是一種變數,在使用互斥鎖時,實際上是對這個變數進行置0置1操作並進行判斷使得執行緒能夠獲得鎖或釋放鎖。
    (互斥鎖的具體實現在文末講解)
    2、作用:互斥鎖的作用是對臨界區加以保護,以使任意時刻只有一個執行緒能夠執行臨界區的程式碼。實現了多執行緒之間的互斥。
    3、介面:
    使用互斥鎖主要有以下幾個介面操作
//兩種方法對鎖進行初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
              const pthread_mutexattr_t *restrict attr);
//互斥鎖的銷燬
int pthread_mutex_destroy(pthread_mutex_t *mutex);

//獲得鎖
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);

//釋放鎖
int pthread_mutex_unlock(pthread_mutex_t *mutex);

兩種獲得鎖方法的比較:
pthread_mutex_lock:如果此時已經有另一個執行緒已經獲得了鎖,那麼當前執行緒呼叫該函式後就會被掛起等待,直到有另一個執行緒釋放了鎖,該執行緒會被喚醒。
pthread_mutex_trylock:如果此時有另一個賢臣已經獲得了鎖,那麼當前執行緒呼叫該函式後會立即返回並返回設定出錯碼為EBUSY,即它不會使當前執行緒掛起等待。

既然已經有了互斥鎖,我們可以對上述程式碼進行修改,如下:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static int g_val=0;
void* pthread_mem(void* arg)
{
    int i=0;
    int val=0;
    while(i<500000)
    {
        pthread_mutex_lock(&mutex);
        val = g_val;
        i++;
        g_val=val+1;
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

這段程式碼通過對臨界區進行加鎖與解鎖,每個執行緒在進入臨界區的時候獲得一把鎖,操作執行完成後釋放鎖資源,使得其他等待的執行緒能夠抱鎖進入,這樣就確保了每個執行緒進來執行的操作都是原子的,這樣使得最後的結果為1000000.

  • 互斥鎖的底層實現:

上邊已經提到,mutex的本質是一種變數。假設mutex為1時表示鎖是空閒的,此時某個程序如果呼叫lock函式就可以獲得所資源;當mutex為0時表示鎖被其他程序佔用,如果此時有程序呼叫lock來獲得鎖時會被掛起等待。

1、lock和unlock的實現方案一
這裡寫圖片描述
unlock:這個操作是原子的,即通過執行unlock的程式碼後,mutex要麼為1,要麼不為1
lock:執行lock時,先要對mutex進行判斷,如果mutex>0,修改mutex=0,否則就表示鎖被佔用,將當前程序掛起等待。假設mutex為1,且有兩個執行緒A和B來進行lock以獲得鎖,對於A和B來說,他兩都拿到mutex為1,都會進入if()條件內部,此時執行緒A已經將鎖拿到(mutex置為0),而B執行緒並不知道,也將mutex置為0,因此,執行緒A和執行緒B都會認為自己已經獲得了鎖。
對於這種方案,因為lock的過程不是原子的,也會產生錯誤。

2、lock和unlock的實現方案二
使用swap或exchange指令,這個指令的含義是將暫存器和記憶體單元中的資料進行交換,這條指令保證了操作的原子性:
這裡寫圖片描述
lock:這一步先將0賦值到暫存器al,再將mutex與al中的值交換,再進行判斷。當對某個執行緒進行lock操作時,即使在中間任意一步被切出去也沒有問題。這樣就保證了lock的操作也是原子的。

使用互斥鎖引入的問題:

使用互斥鎖可能會導致死鎖問題。

  • 死鎖:
    指在一組程序中的各個程序均佔有不會釋放的資源,但因互相申請被其他程序所佔用不會釋放的資源而處於的一種永久等待狀態。通俗一點來講,假設A執行緒持有鎖a,B執行緒持有鎖b,而執行緒訪問臨界區的條件時同時具有鎖a和鎖b,那麼A就會等待B釋放鎖b,B會等待A釋放鎖a,如果沒有一種措施,他兩會一直等待,這樣就產生了死鎖。
  • 死鎖產生的情況
    1、系統資源不足:如果系統資源足夠,每個申請鎖的執行緒都能後獲得鎖,那麼產生死鎖的情況就會大大降低;
    2、申請鎖的順序不當:當兩個執行緒按照不同的順序申請、釋放鎖資源時也會產生死鎖。( 自行執行以下程式碼觀察現象。)
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

int a=0;
int b=0;
pthread_mutex_t mutex_a;
pthread_mutex_t mutex_b;

void *another(void* arg)
{
    pthread_mutex_lock(&mutex_b);
    printf("new_thread,got mutex_b,waiting for mutex_a\n");
    sleep(5);
    ++b;
    pthread_mutex_lock(&mutex_a);
    b += a++;
    pthread_mutex_unlock(&mutex_a);
    pthread_mutex_unlock(&mutex_b);
    pthread_exit(NULL);
}

int main()
{
    pthread_t id;
    pthread_mutex_init(&mutex_a, NULL);
    pthread_mutex_init(&mutex_b, NULL);
    pthread_create(&id, NULL, another, NULL);

    pthread_mutex_lock(&mutex_a);
    printf("main_thread,got mutex_a,waiting for mutex_b\n");
    sleep(5);
    ++a;
    pthread_mutex_lock(&mutex_b);
    a += b++;
    pthread_mutex_unlock(&mutex_b);
    pthread_mutex_unlock(&mutex_a);

    pthread_join(id, NULL);
    pthread_mutex_destroy(&mutex_a);
    pthread_mutex_destroy(&mutex_b);
    return 0;
}

在上述程式碼中,主執行緒先申請了mutex_a,再申請mutex_b,而在新執行緒中,新執行緒先申請mutex_b,再申請mutex_a,這樣雙方各自持有一把鎖,並互相等待對方的鎖,就產生了死鎖。執行結果如下:
這裡寫圖片描述

  • 死鎖產生的條件
    1、互斥屬性:即每次只能有一個執行緒佔用資源。
    2、請求與保持:即已經申請到鎖資源的執行緒可以繼續申請。在這種情況下,一個執行緒也可以產生死鎖情況,即抱著鎖找鎖。
    3、不可剝奪:執行緒已經得到所資源,在沒有自己主動釋放之前,不能被強行剝奪。
    4、迴圈等待:多個執行緒形成環路等待,每個執行緒都在等待相鄰執行緒的鎖資源。
  • 死鎖的避免:
    1、既然死鎖的產生是由於使用了鎖,那麼在能不使用鎖的情況下就儘量不使用,如果有多種方案都能實現,那麼儘量不選用帶鎖的這種方案
    2、儘量避免同時獲得多把鎖,如果有必要,就要保證獲得鎖的順序相同

相關推薦

執行同步互斥讀寫

讀寫鎖基本原理 當有一個執行緒已經持有互斥鎖時,互斥鎖將所有試圖進入臨界區的執行緒都阻塞住。但是考慮一種情形,當前持有互斥鎖的執行緒只是要讀訪問共享資源,而同時有其它幾個執行緒也想讀取這個共享資源,但是由於互斥鎖的排它性,所有其它執行緒都無法獲取鎖,也就無法讀訪問共享資源

執行同步互斥

多執行緒執行緒基礎操作 關於本篇部落格的更多程式碼:GitHub連結 執行緒的同步與互斥,學習生產者消費者模型及應用場景 執行緒安全:生產者與消費者模型,讀寫者模型,同步與互斥的實現,互斥鎖,條件變數,posix訊號量,讀寫鎖,自旋鎖 大部分情況,執行緒使用的資料都是區域性變

Windows執行(四)執行同步互斥問題

執行緒同步與互斥的測試函式如下所示: #include <stdio.h> #include <process.h> #include <Windows.h> #define THREAD_NUM 10 unsigned long g_nNum

執行同步互斥(3)

在進行多執行緒程式設計時,難免還要碰到兩個問題,那就執行緒間的互斥與同步: 執行緒同步是指執行緒之間所具有的一種制約關係,一個執行緒的執行依賴另一個執行緒的訊息,當它沒有得到另一個執行緒的訊息時應等待,直到訊息到達時才被喚醒。 執行緒互斥是指對於共享的程序系統資源,在各單個執行緒訪問時的排它性。當有若干個執行

Java多執行--同步synchronized;等待喚醒wait、notify、notifyAll;生命週期

class Info{ // 定義資訊類 private String name = "李興華"; // 定義name屬性 private String content = "JAVA講師" ; // 定義content屬性 private boolean flag = false ; // 設

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

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

Linux 多執行同步互斥

1.同步 同一個程序中的多個執行緒共享所在程序的記憶體資源,當多個執行緒在同一時刻同時訪問同一種共享資源時,需要相互協調,以避免出現數據的不一致和覆蓋等問題,執行緒之間的協調和通訊的就叫做執行緒的同步問題, 執行緒同步的思路: 讓多個執行緒依次訪問共享資源,而不是並行 我

Java核心(三)併發中的執行同步

樂觀鎖、悲觀鎖、公平鎖、自旋鎖、偏向鎖、輕量級鎖、重量級鎖、鎖膨脹...難理解?不存的!來,話不多說,帶你飆車。 上一篇介紹了執行緒池的使用,在享受執行緒池帶給我們的效能優勢之外,似乎也帶來了另一個問題:執行緒安全的問題。 那什麼是執行緒的安全問題呢? 一、執行緒安全問題的產生 執行緒安全問題:指的是

Java核心-併發中的執行同步

一、執行緒安全問題的產生 執行緒安全問題:指的是在多執行緒程式設計中,同時操作同一個可變的資源之後,造成的實際結果與預期結果不一致的問題。 比如:A和B同時向C轉賬10萬元。如果轉賬操作不具有原子性,A在向C轉賬時,讀取了C的餘額為20萬,然後加上轉賬的10萬,計算出此時應該有30萬,

執行同步(一)

   im專案中都會存在離線訊息,我們在接受到訊息後,開啟子執行緒,處理相關業務邏輯。因為業務邏輯需遵循一定的處理順序,我們將部分程式碼加上了鎖。但是在離線訊息太多時,卻出現了執行緒問題:OutOfMemoryError: pthread_create (1040KB sta

Java基礎加強之多執行篇 - 執行建立終止、互斥、通訊、本地變數

執行緒建立與終止 執行緒建立 Thread類與 Runnable 介面的關係 public interface Runnable { public abstract void run(); } public class Thread implements Run

執行同步--協同方式和互斥方式

參考部落格:http://www.cnblogs.com/kennyMc/archive/2012/12/15/2818887.html    參考部落格:http://www.cnblogs.com/xilentz/archive/2012/11/13/2767317.h

執行同步(3)

執行緒同步與死鎖 1、多執行緒共享資料 在多執行緒操作中,多個執行緒有可能同時處理同一個資源,這就是多執行緒中的共享資料 2、執行緒同步 解決資料共享問題必須使用同步,所謂的同步就是指多個執行緒在同一個時間段內只能有一個執行緒執行指定的程式

【Windows】執行漫談——.NET執行同步之Interlocked和ReadWrite

摘要: 本系列意在記錄Windwos執行緒的相關知識點,包括執行緒基礎、執行緒排程、執行緒同步、TLS、執行緒池等。 這篇來說說靜態的Interlocked類和ReadWrite鎖 .NET中的Interlock

執行同步synchronized

看如下程式碼: package project; public class Demo { public static void main(String []args){ final Out out=new Out(); new Thread(){ publi

OS實驗二 執行同步通訊

1 實驗目的與要求 1、掌握Linux下執行緒的概念; 2、瞭解Linux執行緒同步與通訊的主要機制; 3、通過訊號燈操作實現執行緒間的同步與互斥。 2 實驗內容 通過Linux多執行緒與訊號燈機制,設計並實現計算機執行緒與I/O執行緒共享緩衝區的同步與通訊。

執行同步(windows平臺)事件

一:介紹 事件Event實際上是個核心物件,事件分兩種狀態:激發狀態和未激發狀態。分兩種型別:手動處置事件和自動處置事件。 手動處置事件被設定為激發狀態後,會喚醒所有等待的執行緒,一直保持為激發狀態,

cocos2dx多執行以及執行同步 cocos2dx記憶體管理執行問題

ocos2d-x引擎在內部實現了一個龐大的主迴圈,每幀之間更新介面,如果耗時的操作放到了主執行緒中,遊戲的介面就會卡,這是不能容忍的,遊戲最基本的條件就是流暢性,這就是為什麼遊戲開發選擇C++的原因。另外現在雙核手機和四核手機越來越普遍了,是時候使用多執行緒來挖掘硬體的潛力

[C#學習筆記之多執行2]多執行同步併發訪問共享資源工具—Lock、Monitor、Mutex、Semaphore

“執行緒同步”的含義         當一個程序啟動了多個執行緒時,如果需要控制這些執行緒的推進順序(比如A執行緒必須等待B和C執行緒執行完畢之後才能繼續執行),則稱這些執行緒需要進行“執行緒同步(thread synchronization)”。         執行緒

執行同步非同步的最簡單圖解

前提 執行緒的同步和非同步是針對多核CPU而言的,沒有多核CPU就沒有非同步的概念(此時全是同步的)。 下面以四核CPU為例子,用圖解的方式看同步和非同步的區別: 同步: 可以看出,所謂同步,就是每次只有一個執行緒能去執行,即使有多的計算資源(在