目錄
線程互斥
進程線程間的互斥相關背景概念
互斥量mutex
互斥量的接口
初始化互斥量有兩種方法:
銷毀互斥量
互斥量加鎖和解鎖
改進售票系統
?互斥量實現原理探究
??互斥量的封裝
線程互斥
進程線程間的互斥相關背景概念
- 臨界資源:多線程執行流共享的資源就叫做臨界資源
- 臨界區:每個線程內部,訪問臨界資源的代碼,就叫做臨界區
- 互斥:任何時刻,互斥保證有且只有一個執行流進入臨界區,訪問臨界資源,通常對臨界資源起保護作用
- 原子性:不會被任何調度機制打斷的操作,該操作只有兩態,要么完成,要么未完成
互斥量mutex
大部分情況,線程使用的數據都是局部變量,變量的地址空間在線程棧空間內,這種情況,變量
歸屬單個線程,其他線程無法獲得這種變量。但有時候,很多變量都需要在線程間共享,這樣的變量稱為共享變量,可以通過數據的共享,完成線程之間的交互。多個線程并發的操作共享變量,會帶來一些問題
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
int ticket = 100;
void *route(void *arg)
{char *id = (char *)arg;while (1){if (ticket > 0){usleep(1000);//模擬搶票printf("%s sells ticket:%d\n", id, ticket);//搶到票ticket--;}else{break;}}return nullptr;
}
int main()
{pthread_t t1, t2, t3, t4;pthread_create(&t1, NULL, route,(void*) "thread 1");pthread_create(&t2, NULL, route,(void*) "thread 2");pthread_create(&t3, NULL, route,(void*) "thread 3");pthread_create(&t4, NULL, route,(void*) "thread 4");pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);return 0;
}
上述代碼用多線程模擬搶票,但是為什么搶到負數???
1. if 語句判斷條件為真以后,代碼可以并發的切換到其他線程
if判斷只是對票數進行判斷,在cpu內進行邏輯運算,當某個線程剛載入cpu內判斷時間片到了就被替換成下一個線程,這樣的線程不止一個,再次調度回來時才對--ticket,調度多了,連著減,減成了負數
2.usleep 這個模擬漫長業務的過程,在這個漫長的業務過程中,可能有很多個線程會進入該代碼
段
計算機的運行速度很快,1毫秒內搶票很快,多個線程進入該代碼陷入內核線程又被掛起
3.--ticket 操作本身就不是一個原子操作
在計算機內部,--tecket簡單分為三步驟:操作要把數據從內存加載到cpu,然后cpu進行計算,最后再把結束寫回內存。宏觀上是cpu在進行調度,假如cpu調度時時間片到了,進程阻塞掛起時,把該線程的上下文保存,在下一次調度時拷貝回繼續運行,在此期間還有別的線程在搶票,不斷得對ticket操作,ticket不斷減少,但是上一次還沒有調度完的線程繼續切回來,上一次上下文運行的結果又寫回內存,此時ticket的數值反而又會增大
全局資源沒有被保護,可能會有并發問題,也就是線程安全。要解決以上問題,需要做到三點:
- 代碼必須要有互斥行為:當代碼進入臨界區執行時,不允許其他線程進入該臨界區。
- 如果多個線程同時要求執行臨界區的代碼,并且臨界區沒有線程在執行,那么只能允許一個線程進入該臨界區。
- 如果線程不在臨界區中執行,那么該線程不能阻止其他線程進入臨界區。
要做到這三點,本質上就是需要一把鎖,在內核態返回用戶態時進行檢查。Linux上提供的這把鎖叫互斥量。
互斥量的接口
初始化互斥量有兩種方法:
定義鎖
方法1,靜態分配:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
方法2,動態分配:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
參數:
mutex:要初始化的互斥量
attr:NULL
銷毀互斥量
釋放鎖
int pthread_mutex_destroy(pthread_mutex_t *mutex);
銷毀互斥量需要注意:
- 使用PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要銷毀,全局的鎖程序結束自動釋放,局部鎖才需要手動釋放
- 不要銷毀一個已經加鎖的互斥量
- 已經銷毀的互斥量,要確保后面不會有線程再嘗試加鎖
互斥量加鎖和解鎖
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回0,失敗返回錯誤號
調用 pthread_ lock 時,可能會遇到以下情況:
- 互斥量處于未鎖狀態,該函數會將互斥量鎖定,同時返回成功
- 發起函數調用時,其他線程已經鎖定互斥量,或者存在其他線程同時申請互斥量,但沒有競爭到互斥量,那么pthread_ lock調?會陷入阻塞(執行流被掛起),等待互斥量解鎖。
申請鎖是為了保護臨界資源的安全,多線程競爭申請鎖,首先多進程就要先看到鎖,鎖本身就是臨界資源,所以申請鎖的過程,必須是原子的,成功就繼續向后訪問臨界資源,申請失敗,阻塞掛起申請執行流,等待下一次調度喚醒,鎖本身的能力本質是將臨界代碼區由并行轉而為串行,加鎖之后在臨界區內部,允許線程切換,即使切出去了鎖還沒釋放,也得等我執行完代碼才會釋放鎖,其他線程拿到鎖,開鎖進入臨界區
改進售票系統
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <iostream>
#include <string.h>
int ticket = 100;class ThreadDate
{
public:ThreadDate(const std::string &n, pthread_mutex_t &lock): name(n), lockp(&lock) {}~ThreadDate() {}std::string name;pthread_mutex_t *lockp;
};
void *route(void *arg)
{ThreadDate *td = static_cast<ThreadDate *>(arg);while (1){pthread_mutex_lock(td->lockp); // 加鎖if (ticket > 0){usleep(1000); // 模擬搶票printf("%s sells ticket:%d\n", td->name.c_str(), ticket); // 搶到票ticket--;pthread_mutex_unlock(td->lockp); // 用完解鎖}else{pthread_mutex_unlock(td->lockp); // 票搶完了,解鎖退出,否則線程一直阻塞break;}}return nullptr;
}
int main()
{pthread_mutex_t lock;pthread_mutex_init(&lock, nullptr); // 初始化鎖pthread_t t1, t2, t3, t4;ThreadDate *td1 = new ThreadDate("thread 1", lock);pthread_create(&t1, NULL, route, (void *)td1);ThreadDate *td2 = new ThreadDate("thread 2", lock);pthread_create(&t2, NULL, route, (void *)td2);ThreadDate *td3 = new ThreadDate("thread 3", lock);pthread_create(&t3, NULL, route, (void *)td3);ThreadDate *td4 = new ThreadDate("thread 4", lock);pthread_create(&t4, NULL, route, (void *)td4);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);pthread_mutex_destroy(&lock); // 銷毀鎖return 0;
}
全局鎖
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <iostream>
#include <string.h>
int ticket = 100;pthread_mutex_t glock =PTHREAD_MUTEX_INITIALIZER;//全局初始化
class ThreadDate
{
public:ThreadDate(const std::string &n, pthread_mutex_t &lock): name(n), lockp(&lock) {}~ThreadDate() {}std::string name;pthread_mutex_t *lockp;
};
void *route(void *arg)
{ThreadDate *td = static_cast<ThreadDate *>(arg);while (1){pthread_mutex_lock(&glock); // 加鎖if (ticket > 0){usleep(1000); // 模擬搶票printf("%s sells ticket:%d\n", td->name.c_str(), ticket); // 搶到票ticket--;pthread_mutex_unlock(&glock); // 用完解鎖}else{pthread_mutex_unlock(&glock); // 票搶完了,解鎖退出,否則線程一直阻塞break;}}return nullptr;
}
int main()
{pthread_mutex_t lock;pthread_t t1, t2, t3, t4;ThreadDate *td1 = new ThreadDate("thread 1", lock);pthread_create(&t1, NULL, route, (void *)td1);ThreadDate *td2 = new ThreadDate("thread 2", lock);pthread_create(&t2, NULL, route, (void *)td2);ThreadDate *td3 = new ThreadDate("thread 3", lock);pthread_create(&t3, NULL, route, (void *)td3);ThreadDate *td4 = new ThreadDate("thread 4", lock);pthread_create(&t4, NULL, route, (void *)td4);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);return 0;
}
?
?互斥量實現原理探究
鎖的原理
1硬件實現:關閉時鐘中斷
2.軟件實現:為了實現互斥鎖操作,大多數體系結構都提供了swap或exchange指令,該指令的作用是把寄存器和內存單元的數據相交換
經過上面的例子,大家已經意識到單純的 i++ 或者 ++i 都不是原子的,有可能會有數據一致性問題為了實現互斥鎖操作,大多數體系結構都提供了swap或exchange指令,該指令的作用是把寄存器和內存單元的數據相交換,由于只有一條指令,保證了原子性,即使是多處理器平臺,訪問內存的 總線周期也有先后,一個處理器上的交換指令執行時另?個處理器的交換指令只能等待總線周期。 現在我們把lock和unlock的偽代碼改一下
鎖的初始化把mutex初始化為1,申請鎖的時候先把0寫進al,然后交換?al和mutex鎖的數據,此時%al里面是1,mutex是0,申請鎖成功在返回,然后訪問臨界資源,時間片到了,掛起放到調度隊列里了,也沒有關系,鎖還沒有釋放,上下文數據被帶走了。其他的線程都執行move,寄存器和mutex內都是0,交換完還是0,全部進了下一個調度隊列,直到擁有鎖的線程執行完釋放鎖,把1寫回mutex,才能輪到下一個線程申請鎖成功,如果沒有申請成功,鎖被占用了,執行else掛起等待,等擁有鎖的釋放鎖后,才可以執行后面的代碼申請鎖區訪問臨界支援,說白了,誰交換走了1,誰就持有鎖,誰就有優先訪問臨界資源的權力
??互斥量的封裝
1.0
//Mutex.hpp#include <pthread.h>
#include <iostream>
namespace MutexModule
{class Mutex{public:Mutex(){pthread_mutex_init(&_mutex, nullptr);}void Lock(){int n = pthread_mutex_lock(&_mutex);(void)n;}void Unlock(){int n = pthread_mutex_unlock(&_mutex);(void)n;}~Mutex(){pthread_mutex_destroy(&_mutex);}private:pthread_mutex_t _mutex;};
}
//TextMutex.cc
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <iostream>
#include <string.h>
#include "Mutex.hpp"
using namespace MutexModule;
int ticket = 100;pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER; // 全局初始化
class ThreadDate
{
public:ThreadDate(const std::string &n, Mutex &lock): name(n), lockp(&lock) {}~ThreadDate() {}std::string name;Mutex *lockp;
};
void *route(void *arg)
{ThreadDate *td = static_cast<ThreadDate *>(arg);while (1){td->lockp->Lock(); // 加鎖if (ticket > 0){usleep(1000); // 模擬搶票printf("%s sells ticket:%d\n", td->name.c_str(), ticket); // 搶到票ticket--;td->lockp->Unlock(); // 用完解鎖}else{td->lockp->Unlock();; // 票搶完了,解鎖退出,否則線程一直阻塞break;}}return nullptr;
}
int main()
{Mutex lock;pthread_t t1, t2, t3, t4;ThreadDate *td1 = new ThreadDate("thread 1", lock);pthread_create(&t1, NULL, route, (void *)td1);ThreadDate *td2 = new ThreadDate("thread 2", lock);pthread_create(&t2, NULL, route, (void *)td2);ThreadDate *td3 = new ThreadDate("thread 3", lock);pthread_create(&t3, NULL, route, (void *)td3);ThreadDate *td4 = new ThreadDate("thread 4", lock);pthread_create(&t4, NULL, route, (void *)td4);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);return 0;
}
2.0
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <iostream>
#include <string.h>
#include "Mutex.hpp"
using namespace MutexModule;
int ticket = 100;pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER; // 全局初始化
class ThreadDate
{
public:ThreadDate(const std::string &n, Mutex &lock): name(n), lockp(&lock) {}~ThreadDate() {}std::string name;Mutex *lockp;
};
void *route(void *arg)
{ThreadDate *td = static_cast<ThreadDate *>(arg);while (1){LockGuard guard(*td->lockp);if (ticket > 0){usleep(1000); // 模擬搶票printf("%s sells ticket:%d\n", td->name.c_str(), ticket); // 搶到票ticket--;}else{td->lockp->Unlock();break;}}return nullptr;
}
int main()
{Mutex lock;pthread_t t1, t2, t3, t4;ThreadDate *td1 = new ThreadDate("thread 1", lock);pthread_create(&t1, NULL, route, (void *)td1);ThreadDate *td2 = new ThreadDate("thread 2", lock);pthread_create(&t2, NULL, route, (void *)td2);ThreadDate *td3 = new ThreadDate("thread 3", lock);pthread_create(&t3, NULL, route, (void *)td3);ThreadDate *td4 = new ThreadDate("thread 4", lock);pthread_create(&t4, NULL, route, (void *)td4);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);return 0;
}
#include <pthread.h>
#include <iostream>
namespace MutexModule
{class Mutex{public:Mutex(){pthread_mutex_init(&_mutex, nullptr);}void Lock(){int n = pthread_mutex_lock(&_mutex);(void)n;}void Unlock(){int n = pthread_mutex_unlock(&_mutex);(void)n;}~Mutex(){pthread_mutex_destroy(&_mutex);}private:pthread_mutex_t _mutex;};class LockGuard{public:LockGuard(Mutex &mutex):_mutex(mutex){_mutex.Lock();}~LockGuard(){_mutex.Unlock();}private:Mutex &_mutex;};
}