網(wǎng)站制作報(bào)價(jià)明細(xì)表bt磁力狗
目錄
1.線程的互斥
1.1.進(jìn)程線程間的互斥相關(guān)背景概念
1.2.互斥量mutex的基本概念
所以多線程之間為什么要有互斥?
為什么搶票會(huì)搶到負(fù)數(shù),無法獲得正確結(jié)果?
為什么--操作不是原子性的呢?
解決方式:
2.三種加鎖的方式
2.1全局變量(靜態(tài)分布)的鎖
2.2局部變量(動(dòng)態(tài)分布)的鎖
2.3.銷毀鎖(互斥量)的方式:
2.4.互斥量加鎖和解鎖
2.5?RAII風(fēng)格的鎖
代碼:
3.互斥的底層實(shí)現(xiàn)?
1.線程的互斥
1.1.進(jìn)程線程間的互斥相關(guān)背景概念
- 臨界資源:多線程執(zhí)行流共享的資源就叫做臨界資源
- 臨界區(qū):每個(gè)線程內(nèi)部,訪問臨界資源的代碼,就叫做臨界區(qū)
- 互斥:任何時(shí)刻,互斥保證有且只有一個(gè)執(zhí)行流進(jìn)入臨界區(qū),訪問臨界資源,通常對(duì)臨界資源起保護(hù)作用
- 原子性(后面討論如何實(shí)現(xiàn)):不會(huì)被任何調(diào)度機(jī)制打斷的操作,該操作只有兩態(tài),要么完成,要么未完成
1.2.互斥量mutex的基本概念
- 大部分情況,線程使用的數(shù)據(jù)都是局部變量,變量的地址空間在線程??臻g內(nèi),這種情況,變量歸屬單個(gè)線程,其他線程無法獲得這種變量。
- 但有時(shí)候,很多變量都需要在線程間共享,這樣的變量稱為共享變量,可以通過數(shù)據(jù)的共享,完成線程之間的交互。
- 多個(gè)線程并發(fā)的操作共享變量,會(huì)帶來一些問題
所以多線程之間為什么要有互斥?
上面概念有些抽象,我們來看一個(gè)實(shí)際的例子方便我們理解——搶票系統(tǒng):
代碼:
// 操作共享變量會(huì)有問題的售票系統(tǒng)代碼
#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;
}
}
}
int main( void )
{
pthread_t t1, t2, t3, t4;
pthread_create(&t1, NULL, route, "thread 1");
pthread_create(&t2, NULL, route, "thread 2");
pthread_create(&t3, NULL, route, "thread 3");
pthread_create(&t4, NULL, route, "thread 4");
pthread_join(t1, NULL);
pthread_join(t2, NULL);pthread_join(t3, NULL);
pthread_join(t4, NULL);
}
執(zhí)行結(jié)果:
這是沒有加鎖(互斥)的代碼執(zhí)行的結(jié)果,發(fā)現(xiàn)我們搶票搶著搶著竟然搶到了負(fù)數(shù)!這是萬萬不行的。
為什么搶票會(huì)搶到負(fù)數(shù),無法獲得正確結(jié)果?
共享資源被訪問的時(shí)候,沒有被保護(hù),并且本身操作不是原子的!
- if 語句判斷條件為真以后,代碼可以并發(fā)的切換到其他線程
- usleep 這個(gè)模擬漫長(zhǎng)業(yè)務(wù)的過程,在這個(gè)漫長(zhǎng)的業(yè)務(wù)過程中,可能有很多個(gè)線程會(huì)進(jìn)入該代碼段
- --ticket 操作本身就不是一個(gè)原子操作
前兩者我們好理解,
為什么--操作不是原子性的呢?
ticket需要先從內(nèi)存中讀取數(shù)據(jù)放在CPU上,然后CPU進(jìn)行加法或者減法操作,最后再將數(shù)據(jù)放在內(nèi)存當(dāng)中。因此就不是原子性的。
-- 操作并不是原子操作,而是對(duì)應(yīng)三條匯編指令:
- load :將共享變量ticket從內(nèi)存加載到寄存器中
- update : 更新寄存器里面的值,執(zhí)行-1操作
- store :將新值,從寄存器寫回共享變量ticket的內(nèi)存地址
解決方式:
要解決以上問題,需要做到三點(diǎn):
- 代碼必須要有互斥行為:當(dāng)代碼進(jìn)入臨界區(qū)執(zhí)行時(shí),不允許其他線程進(jìn)入該臨界區(qū)。
- 如果多個(gè)線程同時(shí)要求執(zhí)行臨界區(qū)的代碼,并且臨界區(qū)沒有線程在執(zhí)行,那么只能允許一個(gè)線程進(jìn)入該臨界區(qū)。
- 如果線程不在臨界區(qū)中執(zhí)行,那么該線程不能阻止其他線程進(jìn)入臨界區(qū)。
要做到這三點(diǎn),本質(zhì)上就是需要一把鎖。Linux上提供的這把鎖叫互斥量。
2.三種加鎖的方式
2.1全局變量(靜態(tài)分布)的鎖
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
注意這種鎖是定義在全局代碼段的,這種鎖也不需要銷毀
2.2局部變量(動(dòng)態(tài)分布)的鎖
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict
attr);
參數(shù):
mutex:要初始化的互斥量
attr:NULL
這種鎖需要我們?cè)诰植看a段進(jìn)行定義和初始化,并且也需要我們自己去手動(dòng)銷毀。
2.3.銷毀鎖(互斥量)的方式:
int pthread_mutex_destroy(pthread_mutex_t *mutex)
注意以上這兩種鎖的使用都是需要在指定加鎖的區(qū)域進(jìn)行加鎖和解鎖。
2.4.互斥量加鎖和解鎖
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回0,失敗返回錯(cuò)誤號(hào)
2.5?RAII風(fēng)格的鎖
C++注重RAII的編程思想,所以我們可以將鎖自己封裝成為一個(gè)RAII風(fēng)格的鎖
我們可以將鎖進(jìn)行封裝,定義一個(gè)LockGuard的類,里面只有一個(gè)鎖的成員變量,構(gòu)造函數(shù)是加鎖,析構(gòu)函數(shù)是解鎖,所以我們可以創(chuàng)建一個(gè)局部的對(duì)象,讓編譯器自己去調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù),這樣就不需要我們進(jìn)行加鎖和解鎖
代碼:
#ifndef __LOCK_GUARD_HPP__
#define __LOCK_GUARD_HPP__#include <iostream>
#include <pthread.h>class LockGuard
{
public:LockGuard(pthread_mutex_t *mutex):_mutex(mutex){pthread_mutex_lock(_mutex); // 構(gòu)造加鎖}~LockGuard(){pthread_mutex_unlock(_mutex);//析構(gòu)解鎖}
private:pthread_mutex_t *_mutex;
};#endif
在我們學(xué)習(xí)了如何加鎖之后,我們就可以將搶票系統(tǒng)進(jìn)行進(jìn)一步的優(yōu)化:
void route(ThreadData *td)
{while (true){{ // 擔(dān)心就用這個(gè)LockGuard guard(&td->_mutex); // 臨時(shí)對(duì)象, RAII風(fēng)格的加鎖和解鎖//std::lock_guard<std::mutex> lock(td->_mutex);//pthread_mutex_lock(&td->_mutex);if (td->_tickets > 0) // 1{usleep(1000);printf("%s running, get tickets: %d\n", td->_name.c_str(), td->_tickets); // 2td->_tickets--; // 3//pthread_mutex_unlock(&td->_mutex);td->_total++;}else{//pthread_mutex_unlock(&td->_mutex);//td->_mutex.unlock();break;}}}
}
執(zhí)行結(jié)果:
可以看出,加鎖之后就完美解決了票數(shù)會(huì)搶到負(fù)數(shù)的問題!
3.互斥的底層實(shí)現(xiàn)?
- 經(jīng)過上面的例子,大家已經(jīng)意識(shí)到單純的 i++ 或者 ++i 都不是原子的,有可能會(huì)有數(shù)據(jù)一致性問題
- 為了實(shí)現(xiàn)互斥鎖操作,大多數(shù)體系結(jié)構(gòu)都提供了swap或exchange指令,該指令的作用是把寄存器和內(nèi)存單元的數(shù)據(jù)相交換,由于只有一條指令,保證了原子性,即使是多處理器平臺(tái),訪問內(nèi)存的 總線周期也有先后,一個(gè)處理器上的交換指令執(zhí)行時(shí)另一個(gè)處理器的交換指令只能等待總線周期。?
所有線程在爭(zhēng)鎖的時(shí)候,只有一個(gè)鎖,交換的過程,只有一條是匯編——所以該過程是原子的
CPU寄存器硬件只有一套,但是CPU寄存器內(nèi)部的數(shù)據(jù),數(shù)據(jù)線程的硬件上下文是有多套的。
數(shù)據(jù)在內(nèi)存中,所有的線程都能訪問,屬于共享的。但是如果轉(zhuǎn)移到CPU內(nèi)部寄存器中,就屬于一個(gè)線程私有的了!!!