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