文章目錄
- 📝線程互斥
- 🌠 庫函數strncpy
- 🌉進程線程間的互斥相關背景概念
- 🌉互斥量mutex
- 🌠線程同步
- 🌉條件變量
- 🌉同步概念與競態條件
- 🌉 條件變量函數
- 🚩總結
📝線程互斥
🌠 庫函數strncpy
🌉進程線程間的互斥相關背景概念
- 臨界資源:多線程執?流共享的資源就叫做臨界資源
- 臨界區:每個線程內部,訪問臨界資源的代碼,就叫做臨界區
- 互斥:任何時刻,互斥保證有且只有?個執?流進?臨界區,訪問臨界資源,通常對臨界資源起保護作?
- 原?性(后?討論如何實現):不會被任何調度機制打斷的操作,該操作只有兩態,要么完成,要么未完成
🌉互斥量mutex
- ?部分情況,線程使?的數據都是局部變量,變量的地址空間在線程棧空間內,這種情況,變量歸屬單個線程,其他線程?法獲得這種變量。
- 但有時候,很多變量都需要在線程間共享,這樣的變量稱為共享變量,可以通過數據的共享,完成線程之間的交互。
- 多個線程并發的操作共享變量,會帶來?些問題。
Makefile文件
bin=ticket
cc=g++
src=$(wildcard *.cc)
obj=$(src:.cc=.o)$(bin):$(obj)$(cc) -o $@ $^ -lpthread
%.o:%.cc@echo "Comiling $< to $@"$(cc) -c $< -std=c++17.PHONY:clean
clean:rm -f $(bin) $(obj).PHONY:test
test:echo $(src)echo $(obj)
代碼:
#include <stdio.h>
#include <string>
#include <string.h>
#include <pthread.h>
#include <unistd.h>int ticket = 100;void* routine(void* args)
{char *id = (char*)args;// std::string id = static_cast<const char*>(args);while(1){if(ticket > 0){usleep(10000);printf("%s sells ticket:%d\n", id, ticket);ticket--;}else{break;}}return nullptr;
}int main(void)
{pthread_t t1, t2 , t3, t4;pthread_create(&t1, nullptr, routine, (void*)"thread 1");pthread_create(&t2, nullptr, routine, (void*)"thread 2");pthread_create(&t3, nullptr, routine, (void*)"thread 3");pthread_create(&t4, nullptr, routine, (void*)"thread 4");pthread_join(t1, nullptr);pthread_join(t2, nullptr);pthread_join(t3, nullptr);pthread_join(t4, nullptr);return 0;
}
if
語句判斷條件為真以后,代碼可以并發的切換到其他進程usleep
這個模擬夜漫長業務的過程這個漫長的業務過程中,可能有多個線程會進入該代碼段--ticket
操作本身就不是一個原子操作
取出ticket–部分的匯編代碼
objdump -d a.out > test.objdump152 40064b: 8b 05 e3 04 20 00 mov 0x2004e3(%rip),%eax
600b34 <ticket>153 400651: 83 e8 01 sub $0x1,%eax
154 400654: 89 05 da 04 20 00 mov %eax,0x2004da(%rip)
600b34 <ticket>
--
操作并不是原子操作而是對應三條匯編指令:
load
將共享變量體的從內存加載到寄存器update
更新寄存器里面的只執行復議操作- store:將新值從寄存器寫回共享變量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:這是一個指向 pthread_mutexattr_t 類型對象的指針,該類型用于定義互斥鎖的屬性。如果將其設置為 NULL
銷毀互斥量
銷毀互斥量需要注意:
- 使用
PTHREAD_ MUTEX_ INITIALIZER
初始化的互斥量不需要銷毀 - 不要銷毀?個已經加鎖的互斥量
- 已經銷毀的互斥量,要確保后?不會有線程再嘗試加鎖
int pthread_mutex_destroy(pthread_mutex_t *mutex);
互斥量加鎖和解鎖
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回o,失敗返回錯誤號
調用pthread_ lock
時,可能會遇到以下情況:
- 互斥量處于未鎖狀態,該函數會將互斥量鎖定,同時返回成功
- 發起函數調用時,其他線程已經鎖定互斥量,或者存在其他線程同時申請互斥量,但沒有競爭到互斥量,那么pthread_lock調用會陷入阻塞(執行流被掛起),等待互斥量解鎖。
改進上面的售票系統:
#include <stdio.h>
#include <string>
#include <string.h>
#include <pthread.h>
#include <unistd.h>int ticket = 100;
pthread_mutex_t mutex;void* routine(void* args)
{char *id = (char*)args;// std::string id = static_cast<const char*>(args);while(1){pthread_mutex_lock(&mutex);if(ticket > 0){usleep(1000);printf("%s sells ticket:%d\n", id, ticket);ticket--;pthread_mutex_unlock(&mutex);}else{pthread_mutex_unlock(&mutex);break;}}return nullptr;
}int main(void)
{pthread_t t1, t2 , t3, t4;pthread_create(&t1, nullptr, routine, (void*)"thread 1");pthread_create(&t2, nullptr, routine, (void*)"thread 2");pthread_create(&t3, nullptr, routine, (void*)"thread 3");pthread_create(&t4, nullptr, routine, (void*)"thread 4");pthread_join(t1, nullptr);pthread_join(t2, nullptr);pthread_join(t3, nullptr);pthread_join(t4, nullptr);pthread_mutex_destroy(&mutex);return 0;
}
RAII風格的互斥鎖,C++11也有,比如:
std : : mutex mtx;
std : : lock_guard guard ( mtx ) ;
🌠線程同步
🌉條件變量
- 當?個線程互斥地訪問某個變量時,它可能發現在其它線程改變狀態之前,它什么也做不了。
- 例如?個線程訪問隊列時,發現隊列為空,它只能等待,只到其它線程將?個節點添加到隊列中。這種情況就需要?到條件變量。
🌉同步概念與競態條件
- 同步:在保證數據安全的前提下,讓線程能夠按照某種特定的順序訪問臨界資源,從?有效避免饑餓問題,叫做同步
- 競態條件:因為時序問題,?導致程序異常,我們稱之為競態條件。在線程場景下,這種問題也
不難理解
🌉 條件變量函數
初始化
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t*restrict attr);
參數:
cond:要初始化的條件變量
attr: NULL
銷毀:
int pthread_cond_destroy(pthread_cond_t *cond)
等待條件滿?
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrictmutex);
參數:
cond:要在這個條件變量上等待
mutex:互斥量,后面詳細解釋
喚醒等待
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
簡單案例:
- 我們先使用
PTHREAD_COND/MUTEX_INITIALIZER
進行測試,對其他細節暫不追究 - 然后將接口更改成為使用
pthread_cond_init/pthread_cond_destroy
的方式,方便后續進行封裝
#include <iostream>
#include <string.h>
#include <unistd.h>
#include <pthread.h>pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;void* active(void* args)
{std::string name = static_cast<const char*>(args);while(true){pthread_mutex_lock(&mutex);pthread_cond_wait(&cond, &mutex);std::cout<< name << "活動..." << std::endl;pthread_mutex_unlock(&mutex);}
}int main()
{pthread_t t1, t2;pthread_create(&t1, nullptr, active, (void*)"thread -1");pthread_create(&t2, nullptr, active, (void*)"thread -2");sleep(3);while(true){//對比測試pthread_cond_signal(&cond);//喚醒一個線程// pthread_cond_broadcast(&cond);//喚醒所有線程sleep(1);}pthread_join(t1, nullptr);pthread_join(t2, nullptr);pthread_cond_destroy(&cond);pthread_mutex_destroy(&mutex);return 0;
}