最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Linux線程同步/互斥過程詳解

 更新時(shí)間:2025年08月01日 16:49:08   作者:QQ_437664314  
文章講解多線程并發(fā)訪問導(dǎo)致競態(tài)條件,需通過互斥鎖、原子操作和條件變量實(shí)現(xiàn)線程安全與同步,分析死鎖條件及避免方法,并介紹RAII封裝技術(shù)提升資源管理效率

01. 資源共享問題

1.1 多線程并發(fā)訪問

例: 初始狀態(tài)counter=0,線程 1 和 2 各自都執(zhí)行counter++操作

要想對counter++做修改,在底層被編譯成三條機(jī)器指令:

  1. 從內(nèi)存加載counter的值到寄存器(LOAD)
  2. 寄存器中的值加1(ADD)
  3. 將寄存器的值寫回內(nèi)存(STORE)

假設(shè)counter初始值為0,在兩個(gè)線程同時(shí)執(zhí)行的時(shí)候,可能出現(xiàn)下面這種情況。以至于多線程場景中對全局變量并發(fā)訪問不是 100%可靠的。

線程 1 執(zhí)行

  • 從內(nèi)存讀取counter=0到寄存器。
  • 寄存器中counter+1=1未寫回內(nèi)存,就切換到另外一個(gè)線程。

線程 2 執(zhí)行

  • 從內(nèi)存讀取counter=0(因線程 1 未更新內(nèi)存)。
  • 寄存器中counter+1=1,寫回內(nèi)存,此時(shí)counter=1

線程 1 恢復(fù)執(zhí)行

  • 將寄存器中已計(jì)算的1寫回內(nèi)存,覆蓋線程 2 的更新

最終結(jié)果counter=1(預(yù)期應(yīng)為 2)。

1.2 臨界區(qū)與臨界資源

  • 臨界資源:多線程執(zhí)行流共享的資源就叫做臨界資源
  • 臨界區(qū):每個(gè)線程內(nèi)部,訪問臨界資源的代碼,就叫做臨界區(qū),例如上文中的counter++。
  • 互斥:任何時(shí)刻,互斥保證有且只有一個(gè)執(zhí)行流進(jìn)入臨界區(qū),訪問臨界資源,通常對臨界資源起保護(hù)作用
  • 原子性(后面討論如何實(shí)現(xiàn)):不會被任何調(diào)度機(jī)制打斷的操作,該操作只有兩態(tài),要么完成,要么未完成

1.3 鎖的引入

對于臨界資源訪問時(shí)的安全問題,也可以通過加鎖來保證,實(shí)現(xiàn)多線程間的互斥訪問,互斥鎖就是解決多線程并發(fā)訪問方法之一。

我們可以在線程1進(jìn)入臨界區(qū)之前加鎖,出臨界區(qū)之后解鎖, 這樣可以確保并發(fā)訪問臨界資源時(shí)的線性進(jìn)行,若線程1在對共享資源進(jìn)行操作時(shí)被切換成線程2,線程2也只能阻塞等待解鎖。

注:

  • 加鎖、解鎖是比較耗費(fèi)系統(tǒng)資源的,會在一定程序上降低程序的運(yùn)行速度
  • 加鎖后的代碼是串行化執(zhí)行的,勢必會影響多線程場景中的運(yùn)行速度
  • 所以為了盡可能的降低影響,加鎖粒度要盡可能的細(xì)

02. 多線程案例

2.1 為什么線程需要互斥?

當(dāng)多個(gè)線程同時(shí)訪問共享資源時(shí),可能導(dǎo)致競態(tài)條件,造成數(shù)據(jù)不一致或程序異常。但有時(shí)候,很多變量都需要在線程間共享,這樣的變量稱為共享變量,可以通過數(shù)據(jù)的共享,完成線程之間的交互。而多個(gè)線程并發(fā)的操作共享變量,會帶來一些問題。線程互斥機(jī)制確保在任何時(shí)刻只有一個(gè)線程能訪問共享資源。

#include <stdio.h>
#include <pthread.h>
int counter = 0;
void* increment(void* arg) {
    for (int i = 0; i < 100000; i++) {
        counter++; // 非原子操作}
    return NULL;}
int main() {
    pthread_t t1, t2;
    pthread_create(&t1, NULL, increment, NULL);
    pthread_create(&t2, NULL, increment, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    // 理論結(jié)果為:200000
    printf("Final counter: %d\n", counter); // 實(shí)際輸出通常小于200000
    return 0;
}

在上面代碼里面,我們知道counter是臨界資源,而increment函數(shù)是訪問臨界資源的代碼,亦稱為臨界區(qū)。

理想狀態(tài)下是希望兩個(gè)線程分別對counter100000次。但是由由于非原子操作內(nèi)存可見性問題,當(dāng)兩個(gè)線程同時(shí)執(zhí)行這些指令,可能會出現(xiàn)指令交錯(cuò),導(dǎo)致最終結(jié)果通常會小于預(yù)期200000

要解決以上問題,需要做到三點(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 線程或進(jìn)程切換時(shí)機(jī)?

  1. 時(shí)間片耗盡時(shí)
  2. 有更高優(yōu)先級的進(jìn)程要調(diào)度時(shí)
  3. 通過sleep,從內(nèi)核返回用戶時(shí),會進(jìn)行時(shí)間片是否到達(dá)的檢測,進(jìn)而導(dǎo)致切換

如果鎖對象是全局的或靜態(tài)的,可以用宏:PTHREAD_MUTEX_INITIALIZER初始化,并且不用我們主動destroy;如果鎖對象是局部的,需要用pthread_mutex_init初始化,用pthread_mutex_destroy釋放。

  1. 所有對資源的保護(hù),都是對臨界區(qū)代碼的訪問,因?yàn)橘Y源都是通過代碼訪問的。
  2. 要保證加鎖的細(xì)粒度。
  3. 加鎖就是找到臨界區(qū),對臨界區(qū)進(jìn)行加鎖。

那么相應(yīng)的又有一些問題:

  • 鎖也是全局的共享資源,誰保證鎖的安全?加鎖和解鎖被設(shè)計(jì)為原子的。
  • 如果看待鎖?加鎖本質(zhì)就是對資源的預(yù)定工作,整體使用資源,所以加鎖前先要申請鎖。
  • 如果申請鎖的時(shí)候,鎖已經(jīng)被別的線程拿走了怎么辦?其他線程阻塞等待。
  • 線程在訪問臨界區(qū)的時(shí)候,可不可以被切換?可以,我被切走,其他線程也不能進(jìn)來,因?yàn)槲易叩臅r(shí)候是帶著鎖走的,保證了原子性。

03. 線程互斥

3.1 互斥鎖操作

有以下特點(diǎn)

  • 最簡單的同步原語
  • 只有"鎖定"和"未鎖定"兩種狀態(tài)
  • 同一時(shí)間只允許一個(gè)線程持有鎖
// 初始化(靜態(tài))
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 初始化(動態(tài))
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
// 加鎖/解鎖
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
// 銷毀
int pthread_mutex_destroy(pthread_mutex_t *mutex);

3.2代碼互斥問題優(yōu)化

通過對上面代碼進(jìn)行改進(jìn),我們便可以得到正確的結(jié)果。

細(xì)節(jié): 互斥會給其他線程帶來影響

當(dāng)某個(gè)線程持有[鎖資源】 時(shí),對于其他線程的有意義的狀態(tài):在這兩種狀態(tài)的劃分下,確保了多線程并發(fā)訪問時(shí)的 原子性

  • 鎖被我申請了(其他線程無法獲取)
  • 鎖被我釋放了(其他線程可以獲取鎖)

3.3 互斥鎖原理

lock是原子的,其他線程無法進(jìn)入。 為了實(shí)現(xiàn)互斥鎖操作,大多數(shù)體系結(jié)構(gòu)都提供了swapexchange指令,該指令的作用是把寄存器和內(nèi)存單元的數(shù)據(jù)交換(私有和共享),由于只有一條指令,保證了原子性,即使是多處理器平臺,訪問內(nèi)存的總線周期也有先后,一個(gè)處理器上的交換指令執(zhí)行時(shí)另一個(gè)處理器的交換指令只能等待總線周期。

3.4 多線程封裝

著手編寫一個(gè)小組件: Demo 版線程庫目標(biāo):對 原生線程庫 提供的接口進(jìn)行封裝,進(jìn)一步提高對線程相關(guān)接口的熟練程度既然是封裝,這里的類成員包括:

  • 線程 ID
  • 線程名 name
  • 線程狀態(tài) status
  • 線程回調(diào)函數(shù) fun t
  • 傳遞給回調(diào)函數(shù)的參數(shù) args

3.4.1 thread.hpp編寫

#pragma once

#include <iostream>
#include <string>
#include <pthread.h>
#include <cassert>

// 參數(shù)、返回值為 void 的函數(shù)類型
typedef void *(*func_t)(void *);
const int num = 1024;
class Thread
{
public:
    Thread(func_t func, void *args = nullptr, int number = 0)
        : _func(func), _args(args)
    {
        // 根據(jù)編號寫入名字
        char buf[128];
        snprintf(buf, sizeof buf, "thread-%d", num);
        _name = buf;
        int n = pthread_create(&_tid, nullptr, runHelper, this); // this->Thread*
        assert(n == 0);
        (void)n;
    }
    // 回調(diào)方法
    static void *runHelper(void *args)
    {
        Thread *_this = static_cast<Thread *>(args);
        return _this->callback();
    }
    // 獲取 ID
    pthread_t getTID() const
    {
        return _tid;
    }
    // 獲取線程名
    std::string getName() const
    {
        return _name;
    }
    // 啟動線程
    void run()
    {
        int ret = pthread_create(&_tid, nullptr, runHelper, this );//this 是一個(gè)指向當(dāng)前類類型的常量指針
        if (ret != 0)
        {
            std::cerr << "create thread fail!" << std::endl;
            exit(1); // 創(chuàng)建線程失敗,直接退出
        }
    }
    // 線程等待
    void join()
    {
        int ret = pthread_join(_tid, nullptr);
        if (ret != 0)
        {
            std::cerr << "thread join fail!" << std::endl;
            exit(1); // 等待失敗,直接退出
        }
    }
    void *callback()
    { // 亦指在外調(diào)用的線程處理函數(shù),_args與是否返回值有關(guān)
        return _func(_args);
    }

private:
    pthread_t _tid;    // 線程 ID
    std::string _name; // 線程名
    func_t _func;      // 線程回調(diào)函數(shù)
    void *_args;       // 傳遞給回調(diào)函數(shù)的參數(shù)
};

測試代碼:

#include "thread.hpp"
// 1:線程創(chuàng)建和運(yùn)行
void *basic_task(void *arg){
    int *val = static_cast<int *>(arg);
    std::cout << "線程正在運(yùn)行,初始值為: " << *val << std::endl;
    *val *= 2; // 修改傳入的值
    return nullptr;}
// 2:帶返回值
void *task_with_return(void *arg){
    std::string *msg = new std::string("Hello!");
    return msg;}
int main(){{
        int value = 42;
        Thread t1(basic_task, &value);
        t1.join();
        std::cout << "修改后旳值為: " << value << std::endl; // 應(yīng)該輸出84}
    std::cout << "---------------: " << std::endl;{
        Thread t2(task_with_return);
        void *ret_val = nullptr;
        pthread_join(t2.getTID(), &ret_val); // 直接使用pthread_join獲取返回值
        if (ret_val){
            std::string *msg = static_cast<std::string *>(ret_val);
            std::cout << *msg << std::endl; // 輸出線程返回的消息
            delete msg;                     // 記得釋放內(nèi)存
        }}  return 0;}

結(jié)果如下:

3.5 互斥鎖封裝

我們對鎖進(jìn)行封裝,實(shí)現(xiàn)一個(gè)簡單易用的小組件。利用創(chuàng)建對象時(shí)調(diào)用構(gòu)造函數(shù),對象生命周期結(jié)束時(shí)調(diào)用析構(gòu)函數(shù)的特點(diǎn),融入加鎖、解鎖等操作。更加方便

#pragma once
#include <iostream>
#include <pthread.h>
class Mutex
{
public:
    Mutex(const Mutex &) = delete;
    const Mutex &operator=(const Mutex &) = delete;
    Mutex(){
        int n = pthread_mutex_init(&_lock, nullptr);
    }
    void Lock(){
        int n = pthread_mutex_lock(&_lock);
    }
    void Unlock(){
        int n = pthread_mutex_unlock(&_lock);
    }
    pthread_mutex_t *LockPtr() { return &_lock; }
    ~Mutex(){
        int n = pthread_mutex_destroy(&_lock);
    }

private:
    pthread_mutex_t _lock;
};
class LockGuard{
public:
    LockGuard(Mutex &mutex)
        : _mutex(mutex){
        _mutex.Lock();
    }
    ~LockGuard(){
        _mutex.Unlock();
    }
private:
    Mutex &_mutex; // 在該類下面定義了一個(gè)Mutex類型的引用成員變量,_mutex為變量名
};

3.5.1 RAII風(fēng)格

像這種獲取資源即初始化的風(fēng)格稱為RAII風(fēng)格,非常巧妙的運(yùn)用了類和對象的特性,實(shí)現(xiàn)半自動化操作。

04. 線程同步

當(dāng)一個(gè)線程互斥地訪問某個(gè)變量時(shí),它可能發(fā)現(xiàn)在其它線程改變狀態(tài)之前,它什么也做不了。例如:一個(gè)線程訪問隊(duì)列時(shí),發(fā)現(xiàn)隊(duì)列為空,它只能等待,只到其它線程將一個(gè)節(jié)點(diǎn)添加到隊(duì)列中。這種情況就需要用到條件變量。

同步概念與競態(tài)條件:

  • 同步:在保證數(shù)據(jù)安全的前提下,讓線程能夠按照某種特定的順序訪問臨界資源,從而有效避免饑餓問題,叫做同步
  • 競態(tài)條件:因?yàn)闀r(shí)序問題,而導(dǎo)致程序異常,我們稱之為競態(tài)條件。在線程場景下,這種問題也不難理解

4.1 死鎖

死鎖是指在一組進(jìn)程中的各個(gè)進(jìn)程均占有不會釋放的資源,但因互相申請被其他進(jìn)程所站用不會釋放的資源而處于的一種永久等待狀態(tài)。

4.1.1 死鎖四個(gè)必要條件

  • 互斥條件:一個(gè)資源每次只能被一個(gè)執(zhí)行流使用
  • 請求與保持條件:一個(gè)執(zhí)行流因請求資源而阻塞時(shí),對已獲得的資源保持不放
  • 不剝奪條件:一個(gè)執(zhí)行流已獲得的資源,在末使用完之前,不能強(qiáng)行剝奪
  • 循環(huán)等待條件:若干執(zhí)行流之間形成一種頭尾相接的循環(huán)等待資源的關(guān)系

4.1.2 避免死鎖

  • 破壞死鎖的四個(gè)必要條件
  • 加鎖順序一致
  • 避免鎖未釋放的場景
  • 資源一次性分配

4.1.3 避免死鎖算法

  • 死鎖檢測算法(了解)
  • 銀行家算法(了解

4.2 條件變量

條件變量是線程同步的高級機(jī)制,用于解決"等待特定條件成立"的場景。它總是與互斥鎖配合使用,實(shí)現(xiàn)高效的線程等待-通知機(jī)制。有以下特點(diǎn)

  • 總是與互斥鎖配合使用
  • 解決"等待-通知"問題
  • 避免忙等待(busy-waiting)

操作代碼:

// 初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

// 等待條件滿足(自動釋放關(guān)聯(lián)互斥鎖)
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

// 通知條件
int pthread_cond_signal(pthread_cond_t *cond);      // 喚醒一個(gè)線程
int pthread_cond_broadcast(pthread_cond_t *cond);   // 廣播。。喚醒所有線程

可以把條件變量看作一個(gè)結(jié)構(gòu)體,其中包含一個(gè)隊(duì)列結(jié)構(gòu),用來存儲正在排隊(duì)等候的線程信息,當(dāng)條件滿足時(shí),就會取 隊(duì)頭 線程進(jìn)行操作,操作完成后重新進(jìn)入隊(duì)尾。后續(xù)基于此實(shí)現(xiàn)生產(chǎn)者-消費(fèi)者模型。

簡單使用示例:

#include <pthread.h>
#include <stdio.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//靜態(tài)初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int data_ready = 0;  // 共享?xiàng)l件

void* consumer(void* arg) {
    pthread_mutex_lock(&mutex);
    while (data_ready == 0) {
        printf("Consumer: Waiting...\n");
        pthread_cond_wait(&cond, &mutex); // 阻塞并釋放鎖
    }
    printf("Consumer: Processing data.\n");
    data_ready = 0;
    pthread_mutex_unlock(&mutex);
    return NULL;
}

void* producer(void* arg) {
    sleep(1); // 模擬數(shù)據(jù)準(zhǔn)備時(shí)間
    pthread_mutex_lock(&mutex);
    printf("Producer: Data ready.\n");
    data_ready = 1;
    pthread_cond_signal(&cond); // 喚醒消費(fèi)者
    pthread_mutex_unlock(&mutex);
    return NULL;
}

int main() {
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, consumer, NULL);
    pthread_create(&tid2, NULL, producer, NULL);
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    return 0;
}

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Tomcat無法加載css和js等靜態(tài)資源文件的解決思路

    Tomcat無法加載css和js等靜態(tài)資源文件的解決思路

    Tomcat無法加載css和js等靜態(tài)資源文件的情況想必從事相關(guān)行業(yè)的工作人員都有遇到過吧,接下來為大家介紹下詳細(xì)的解決方法,感興趣的朋友可以參考下
    2013-10-10
  • 詳解基于Linux的LVM無縫磁盤水平擴(kuò)容

    詳解基于Linux的LVM無縫磁盤水平擴(kuò)容

    這篇文章主要介紹了詳解基于Linux的LVM無縫磁盤水平擴(kuò)容,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06
  • 詳解linux根目錄空間不足解決方案

    詳解linux根目錄空間不足解決方案

    本篇文章主要介紹了詳解linux根目錄空間不足解決方案,具有一定的參考價(jià)值,有興趣的可以了解一下。
    2017-04-04
  • Linux gnome桌面環(huán)境切換KDE_Plasma實(shí)現(xiàn)過程

    Linux gnome桌面環(huán)境切換KDE_Plasma實(shí)現(xiàn)過程

    這篇文章主要介紹了Linux gnome桌面環(huán)境切換KDE_Plasma實(shí)現(xiàn)過程,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2026-01-01
  • Apache服務(wù)器關(guān)閉TRACE Method請求方式的方法

    Apache服務(wù)器關(guān)閉TRACE Method請求方式的方法

    這篇文章主要介紹了Apache服務(wù)器關(guān)閉TRACE Method請求方式的方法,因?yàn)橹С衷摲绞降姆?wù)器存在跨站腳本漏洞,需要的朋友可以參考下
    2014-06-06
  • Shell中的單中括號和雙中括號的用法詳解

    Shell中的單中括號和雙中括號的用法詳解

    shell中的中括號在條件測試中是使用最頻繁的,無論是中括號還是雙中括號,可用于一些條件的測試:算術(shù)比較、文件屬性測試、字符串比較。這篇文章主要介紹了Shell中的單中括號和雙中括號的用法詳解,需要的朋友可以參考下
    2022-12-12
  • Windows下PHP安裝路徑配置錯(cuò)誤導(dǎo)致Apache無法啟動的解決方法

    Windows下PHP安裝路徑配置錯(cuò)誤導(dǎo)致Apache無法啟動的解決方法

    這篇文章主要介紹了Windows下PHP安裝路徑配置錯(cuò)誤導(dǎo)致Apache無法啟動的解決方法,本文可以說是一個(gè)引號引發(fā)的悲劇,需要的朋友可以參考下
    2014-09-09
  • Ubuntu日志文件清空的三種方式

    Ubuntu日志文件清空的三種方式

    這篇文章主要介紹了三種清空日志文件的方法:重定向符號、truncate命令和刪除再創(chuàng)建文件,其中,重定向符號和truncate命令是最快的清空方式,下面小編將通過圖文結(jié)合的方式給大家詳細(xì)的講解一下,需要的朋友可以參考下
    2025-04-04
  • 手把手教你在騰訊云上搭建hive3.1.2的方法

    手把手教你在騰訊云上搭建hive3.1.2的方法

    這篇文章主要介紹了手把手教你在騰訊云上搭建hive3.1.2的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • Linux服務(wù)器配置SSH服務(wù)登錄失敗的解決方案

    Linux服務(wù)器配置SSH服務(wù)登錄失敗的解決方案

    文章介紹了配置Linux服務(wù)器SSH遠(yuǎn)程登錄失敗處理機(jī)制的步驟,包括備份原配置文件、安裝pam_tally2統(tǒng)計(jì)登錄失敗次數(shù)、使用Sed命令編輯配置、重啟使配置生效以及封禁登錄失敗五次的賬號
    2026-03-03

最新評論

陇西县| 黎平县| 尼勒克县| 江山市| 宝清县| 区。| 永修县| 长顺县| 闻喜县| 安阳县| 荣成市| 普安县| 玉树县| 中阳县| 颍上县| 扶沟县| 汨罗市| 蒲城县| 临洮县| 会东县| 六盘水市| 南丹县| 宝鸡市| 大姚县| 连城县| 嘉兴市| 金山区| 中阳县| 平远县| 嘉鱼县| 石狮市| 淮安市| 南涧| 元谋县| 苏州市| 丹棱县| 宜阳县| 苗栗县| 县级市| 鲁山县| 蓬安县|