互斥鎖Mutex

執行緒安全

多個執行緒搶同一個資源,就會發生問題,以下程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <unistd.h>
#include <thread>
using namespace std;
void func(string msg) {
  //每一秒印1次,這裡是執行10秒
  for (int i = 0; i < 10; i++) {
    cout << "i = " << i << ", msg =" << msg << endl;
    sleep(1);//停1秒鐘
  }
}
int main() {
  //建立執行緒t1
  thread t1(func, "test test");
  //建立執行緒t2
  thread t2(func, "abcdefg abcdefg");
  //執行緒t1被記憶體釋放
  t1.join();
  //執行緒t2被記憶體釋放
  t2.join();
  return 0;
}
i = 0, msg =test test
i = 0, msg =abcdefg abcdefg
i = 1, msg =test test
i = 1, msg =abcdefg abcdefg
i = 2, msg =abcdefg abcdefg
i = 2, msg =test test
i = 3i = 3, msg =test test, msg =abcdefg abcdefg

i = 4i = 4, msg =abcdefg abcdefg, msg =test test

i = 5, msg =test test
i = 5, msg =abcdefg abcdefg
i = 6, msg =test test
i = 6, msg =abcdefg abcdefg
i = 7, msg =test test
i = 7, msg =abcdefg abcdefg
i = 8, msg =abcdefg abcdefg
i = 8, msg =test test
i = 9, msg =abcdefg abcdefg
i = 9, msg =test test

從執行結果來看,以下二句是二個執行緒搶cout(全域變數)這個資源,導致輸出的結果不正常。

i = 3i = 3, msg =test test, msg =abcdefg abcdefg

i = 4i = 4, msg =abcdefg abcdefg, msg =test test

互斥鎖Mutex

同步(Concurrency Control)是指,多個執行緒協調如何使用同一個資源。

如下圖,每一個人代表一個執行緒,他們都在排隊上同一間廁所(同一個資源),當一個人(一個執行緒)進入廁所,就要把門鎖上,當上完廁所,再把門打開,輪下一個人(下一個執行緒)進去上廁所(同一個資源)。

img

互斥鎖保證同一個時間只有一個人(一個執行緒)使用廁所(同一個資源),當一個執行緒把門鎖上,其它執行緒就在門外排隊等候,等待廁所中的執行緒把門打開(開鎖),輪下一個執行緒進去把門鎖上。

匯入標頭檔

#include <mutex>

互斥鎖全域變數

mutex mtx;

lock() 鎖上

可以把mutex想像成是廁所那個門,若是門是鎖起來的狀態,代表有人(一個thread)在廁所裡面,其它人(threads)就要在門外排隊等待,直到門開鎖,沒有鎖起來的狀態,執行緒呼叫mutex.lock()會自行判斷鎖起來的狀態,若沒鎖上,執行緒會得到鎖,若鎖上,執行緒就要排隊等待。

unlock() 開鎖

只有在廁所中的執行緒才有開鎖的功能,其它在廁所門外排隊的執行緒是沒辦法開鎖。

程式碼

先前的程式,把cout想成廁所,程式碼修改如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <unistd.h>
#include <thread>
#include <mutex>
using namespace std;
//建立mutex互斥鎖
mutex mtx;
void func(string msg) {
  //每一秒印1次,這裡是執行10秒
  for (int i = 0; i < 10; i++) {
    mtx.lock();//把門鎖上
    cout << "i = " << i << ", msg =" << msg << endl;
    sleep(1);//持有鎖後,再休眠1秒
    mtx.unlock();//使用完畢,把門開鎖
  }
}
i = 0, msg =test test
i = 1, msg =test test
i = 2, msg =test test
i = 3, msg =test test
i = 4, msg =test test
i = 5, msg =test test
i = 0, msg =abcdefg abcdefg
i = 1, msg =abcdefg abcdefg
i = 2, msg =abcdefg abcdefg
i = 3, msg =abcdefg abcdefg
i = 4, msg =abcdefg abcdefg
i = 5, msg =abcdefg abcdefg
i = 6, msg =abcdefg abcdefg
i = 7, msg =abcdefg abcdefg
i = 8, msg =abcdefg abcdefg
i = 9, msg =abcdefg abcdefg
i = 6, msg =test test
i = 7, msg =test test
i = 8, msg =test test
i = 9, msg =test test

判斷是那個thread加鎖/解鎖/申請鎖

1
2
3
4
5
6
7
8
9
10
11
12
void func(string msg) {
  //每一秒印1次,這裡是執行10秒
  for (int i = 0; i < 10; i++) {
    cout << "申請鎖的thread = " << this_thread::get_id() << endl;
    mtx.lock();
    cout << "鎖上的thread = " << this_thread::get_id() << endl;
    cout << "i = " << i << ", msg =" << msg << endl;
    sleep(1);//持有鎖後,再休眠1秒
    cout << "開鎖的thread = " << this_thread::get_id() << endl;
    mtx.unlock();
  }
}

類別與mutex

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Student {
  //私有成員變數互斥鎖
  mutex m_mutex;
public:
  void func(string msg) {
    for (int i = 0; i < 10; i++) {
      m_mutex.lock();
      cout << "msg = " << msg << endl;
      m_mutex.unlock();
    }
  }
};
int main() {
  Student student;//建立物件
  thread t1(&Student::func, &student, "student1");
  thread t2(&Student::func, &student, "student2");
  thread t3(&Student::func, &student, "student3");
  thread t4(&Student::func, &student, "student4");
  thread t5(&Student::func, &student, "student5");
  t1.join();
  t2.join();
  t3.join();
  t4.join();
  t5.join();
  return 0;
}
msg = student1
msg = student3
msg = student5
msg = student5
msg = student5
msg = student5
msg = student5
msg = student5
msg = student5
msg = student5
msg = student5
msg = student5
以下截斷

recursive_mutex

以下程式碼,func4()呼叫func3(),但執行結果只印出func4。 若是鎖中有鎖,會有死鎖(Dead lock)問題,mutex必須解鎖才能加鎖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mutex mtx;
void func3() {
  mtx.lock();//加鎖
  cout << "func3" << endl;
  mtx.unlock();//解鎖
}
void func4() {
  mtx.lock();//加鎖
  cout << "func4" << endl;
  func3();//呼叫func3
  mtx.unlock();//解鎖
}
int main() {
  //建立執行緒t1
  thread t1(func4, "test test");
  //執行緒t1被記憶體釋放
  t1.join();
  return 0;
}
func4

改成使用recursive_mutex,就不會有死鎖問題。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
recursive_mutex r_mtx;
void func3() {
  r_mtx.lock();//加鎖
  cout << "func3" << endl;
  r_mtx.unlock();//解鎖
}
void func4() {
  r_mtx.lock();//加鎖
  cout << "func4" << endl;
  func3();//呼叫func3
  r_mtx.unlock();//解鎖
}
int main() {
  //建立執行緒t1
  thread t1(func4);
  //執行緒t1被記憶體釋放
  t1.join();
  return 0;
}
func4
func3

lock_guard

lock_guard是mutex的樣板,只要管加鎖,不用管解鎖,它會在離開有效範圍(Scope)與生命週期(Lifetime),自動解鎖。

語法

1
lock_guard<mutex類別名> mlock(mtx物件名);

把之前的程式碼修改成lock_guard

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void func2(string msg) {
  //每一秒印1次,這裡是執行10秒
  for (int i = 0; i < 10; i++) {
    cout << "申請鎖的thread = " << this_thread::get_id() << endl;
    //有效範圍scope 開始
    {
      lock_guard<mutex> mlock(mtx);//把門鎖上
      cout << "鎖上的thread = " << this_thread::get_id() << endl;
      cout << "i = " << i << ", msg =" << msg << endl;
      sleep(1);//持有鎖後,再休眠1秒
      cout << "開鎖的thread = " << this_thread::get_id() << endl;
    }
    //有效範圍scope 結束
  }
}
int main() {
  //建立執行緒t1
  thread t1(func2, "test test");
  //建立執行緒t2
  thread t2(func2, "abcdefg abcdefg");
  //執行緒t1被記憶體釋放
  t1.join();
  //執行緒t2被記憶體釋放
  t2.join();
  return 0;
}
申請鎖的thread = 0x700005e2a000
鎖上的thread = 0x700005e2a000
i = 0, msg =test test
申請鎖的thread = 0x700005ead000
開鎖的thread = 0x700005e2a000
申請鎖的thread = 0x700005e2a000
鎖上的thread = 0x700005e2a000
i = 1, msg =test test
開鎖的thread = 0x700005e2a000
申請鎖的thread = 0x700005e2a000
鎖上的thread = 0x700005e2a000
i = 2, msg =test test
開鎖的thread = 0x700005e2a000
申請鎖的thread = 0x700005e2a000
鎖上的thread = 0x700005e2a000
i = 3, msg =test test
開鎖的thread = 0x700005e2a000
申請鎖的thread = 0x700005e2a000
鎖上的thread = 0x700005ead000
i = 0, msg =abcdefg abcdefg
開鎖的thread = 0x700005ead000
申請鎖的thread = 0x700005ead000
鎖上的thread = 0x700005ead000
i = 1, msg =abcdefg abcdefg
開鎖的thread = 0x700005ead000
申請鎖的thread = 0x700005ead000
鎖上的thread = 0x700005ead000
i = 2, msg =abcdefg abcdefg
開鎖的thread = 0x700005ead000
申請鎖的thread = 0x700005ead000
鎖上的thread = 0x700005ead000
i = 3, msg =abcdefg abcdefg
開鎖的thread = 0x700005ead000
申請鎖的thread = 0x700005ead000
鎖上的thread = 0x700005ead000
i = 4, msg =abcdefg abcdefg
開鎖的thread = 0x700005ead000
以下截斷

results matching ""

    No results matching ""