国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【linux 多線程并發(fā)】線程本地數(shù)據(jù)存儲的兩種方式,每個線程可以有同名全局私有數(shù)據(jù),以及兩種方式的性能分析

這篇具有很好參考價值的文章主要介紹了【linux 多線程并發(fā)】線程本地數(shù)據(jù)存儲的兩種方式,每個線程可以有同名全局私有數(shù)據(jù),以及兩種方式的性能分析。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

線程本地數(shù)據(jù)(TLS)

?專欄內(nèi)容

  • 參天引擎內(nèi)核架構(gòu)
    本專欄一起來聊聊參天引擎內(nèi)核架構(gòu),以及如何實現(xiàn)多機的數(shù)據(jù)庫節(jié)點的多讀多寫,與傳統(tǒng)主備,MPP的區(qū)別,技術(shù)難點的分析,數(shù)據(jù)元數(shù)據(jù)同步,多主節(jié)點的情況下對故障容災(zāi)的支持。

  • 手寫數(shù)據(jù)庫toadb
    本專欄主要介紹如何從零開發(fā),開發(fā)的步驟,以及開發(fā)過程中的涉及的原理,遇到的問題等,讓大家能跟上并且可以一起開發(fā),讓每個需要的人成為參與者。
    本專欄會定期更新,對應(yīng)的代碼也會定期更新,每個階段的代碼會打上tag,方便階段學(xué)習(xí)。

?開源貢獻(xiàn)

  • toadb開源庫

個人主頁:我的主頁
管理社區(qū):開源數(shù)據(jù)庫
座右銘:天行健,君子以自強不息;地勢坤,君子以厚德載物.

前言

現(xiàn)代的CPU都是多core處理器,而且在intel處理器中每個core又可以多個processor,形成了多任務(wù)并行處理的硬件架構(gòu),在服務(wù)器端的處理器上架構(gòu)又有一些不同,傳統(tǒng)的采用SMP,也就是對稱的多任務(wù)處理架構(gòu),每個任務(wù)都可以對等的訪問所有內(nèi)存,外設(shè)等,而如今在ARM系列CPU上,多采用NUMA架構(gòu),它將CPU核分了幾個組,給每個組的CPU core分配了對應(yīng)的內(nèi)存和外設(shè),CPU訪問對應(yīng)的內(nèi)存和外設(shè)時速度最優(yōu),跨組訪問時性能會降底一些。

隨著硬件技術(shù)的持續(xù)發(fā)展,它們對一般應(yīng)用的性能優(yōu)化能力越來越強,同時對于服務(wù)器軟件的開發(fā),提出更高要求,要想達(dá)到極高的并發(fā)和性能,就需要充分利用當(dāng)前硬件架構(gòu)的特點,對它們進(jìn)行壓榨。那么,我們的應(yīng)用至少也是要采用多任務(wù)架構(gòu),不管是多線程還是多進(jìn)程的多任務(wù)架構(gòu),才可以充分利用硬件的資源,達(dá)到高效的處理能力。

當(dāng)然多任務(wù)框架的采用,不僅僅是多線程的執(zhí)行,需要對多任務(wù)下帶來的問題進(jìn)行處理,如任務(wù)執(zhí)行返回值獲取,任務(wù)間數(shù)據(jù)的傳遞,任務(wù)執(zhí)行次序的協(xié)調(diào);當(dāng)然也不是任務(wù)越多處理越快,要避免線程過多導(dǎo)致操作系統(tǒng)夯住,也要防止任務(wù)空轉(zhuǎn)過快導(dǎo)致CPU使用率飆高。

本專欄主要介紹使用多線程與多進(jìn)程模型,如何搭建多任務(wù)的應(yīng)用框架,同時對多任務(wù)下的數(shù)據(jù)通信,數(shù)據(jù)同步,任務(wù)控制,以及CPU core與任務(wù)綁定等相關(guān)知識的分享,讓大家在實際開發(fā)中輕松構(gòu)建自已的多任務(wù)程序。

概述

linux 系統(tǒng)中 線程是一種經(jīng)量級的任務(wù),同一進(jìn)程的多個線程是共享進(jìn)程內(nèi)存的;當(dāng)我們定義一個全局變量時,它可以被當(dāng)前進(jìn)程下的所有線程訪問,如何來定義一個線程本地的變量呢?

TLS方式

在linux 系統(tǒng)下一般有兩種方式來定義線程本地變量,這一技術(shù)叫做Thread Local Storage, TLS。

  • GCC的__thread關(guān)鍵字
  • 鍵值對API

TLS生命周期

線程本地變量的生命周期與線程的生命周期一樣,當(dāng)線程結(jié)束時,線程本地變量的內(nèi)存就會被回收。

當(dāng)然這里需要特別注意,當(dāng)線程本地變量為指針類型時,動態(tài)分配的內(nèi)存空間,系統(tǒng)并不會自動回收,只是將指針變量置為NULL,為了避免內(nèi)存泄漏,需要在線程退出時主動進(jìn)行清理動作,這將在后面的博文中介紹。

線程pthread結(jié)構(gòu)內(nèi)存

在介紹線程本地變量存儲時,就不得不介紹一下pthread結(jié)構(gòu)的內(nèi)存,它定義了線程的重要數(shù)據(jù)結(jié)構(gòu),描述了用戶狀態(tài)線程的完整信息。

pthread 結(jié)構(gòu)非常復(fù)雜,通過 specific_1stblock 數(shù)組和特定的輔助數(shù)組與 TLS 相關(guān)。

#define PTHREAD_KEY_2NDLEVEL_SIZE       32
#define PTHREAD_KEY_1STLEVEL_SIZE \
  ((PTHREAD_KEYS_MAX + PTHREAD_KEY_2NDLEVEL_SIZE - 1) \
   / PTHREAD_KEY_2NDLEVEL_SIZE)

struct pthread
{
    union
  {
#if !TLS_DTV_AT_TP
    /* This overlaps the TCB as used for TLS without threads (see tls.h).  */
    tcbhead_t header;
#else
    struct
    {
      int multiple_threads;
      int gscope_flag;
    } header;
#endif

    void *__padding[24];
  };

  list_t list;
  pid_t tid;

  ...
  struct pthread_key_data
  {
    /* Sequence number.  We use uintptr_t to not require padding on
       32- and 64-bit machines.  On 64-bit machines it helps to avoid
       wrapping, too.  */
    uintptr_t seq;

    /* Data pointer.  */
    void *data;
  } specific_1stblock[PTHREAD_KEY_2NDLEVEL_SIZE];

  /* Two-level array for the thread-specific data.  */
  struct pthread_key_data *specific[PTHREAD_KEY_1STLEVEL_SIZE];

  /* Flag which is set when specific data is set.  */
  bool specific_used;
  ...
}

__thread 關(guān)鍵字

該關(guān)鍵字可用于在 GCC/Clang 編譯環(huán)境中聲明 TLS 變量, 該關(guān)鍵字不是 C 標(biāo)準(zhǔn),并且因編譯器不同而有差異;

原理介紹

使用 __thread關(guān)鍵字聲明的變量存儲在線程的pthred 結(jié)構(gòu)與堆??臻g之間,也就是說,在內(nèi)存布局方面,從高地址到底層地址的內(nèi)存分布為:pthred結(jié)構(gòu)、可變區(qū)和堆棧區(qū)(堆棧的底部和可變區(qū)的頂部是連續(xù)的);

在這種方式下的線程本地變量,變量的類型不能是復(fù)雜的類型,如C++的class類型,而且動態(tài)申請的變量空間,需要主動釋放,線程結(jié)束時,只是對變量空間回收,而對應(yīng)的動態(tài)內(nèi)存則會泄漏。

代碼舉例

/* 
 * created by senllang 2024/1/1 
 * mail : study@senllang.onaliyun.com 
 * Copyright (C) 2023-2024, senllang
 */
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#define THREAD_NAME_LEN 32
__thread char threadName[THREAD_NAME_LEN];
__thread int delay = 0;

typedef struct ThreadData 
{
    char name[THREAD_NAME_LEN];
    int delay;
}ThreadData;

void *threadEntry(void *arg) 
{
    int ret = 0;
    int i = 0;
    ThreadData * data = (ThreadData *)arg;

    printf("[%lu] thread entered \n", pthread_self());

    strncpy(threadName, data->name, THREAD_NAME_LEN);
    delay = data->delay;

    for(i = 0; i < delay; i++)
    {
        usleep(10);
    }
    printf("[%lu] %s exiting after delay %d.\n", pthread_self(), threadName, delay);
    pthread_exit(&ret);
}

int main(int argc, char *argv[]) 
{
    pthread_t thid1,thid2,thid3;
    void *ret;
    ThreadData args1 = {"thread 1", 50000}, args2 = {"thread 2", 25000}, args3 = {"thread 3", 12500};

    strncpy(threadName, "Main Thread", THREAD_NAME_LEN);

    if (pthread_create(&thid1, NULL, threadEntry, &args1) != 0) 
    {
        perror("pthread_create() error");
        exit(1);
    }

    if (pthread_create(&thid2, NULL, threadEntry, &args2) != 0) 
    {
        perror("pthread_create() error");
        exit(1);
    }

    if (pthread_create(&thid3, NULL, threadEntry, &args3) != 0) 
    {
        perror("pthread_create() error");
        exit(1);
    }

    if (pthread_join(thid1, &ret) != 0) 
    {
        perror("pthread_create() error");
        exit(3);
    }

    if (pthread_join(thid2, &ret) != 0) 
    {
        perror("pthread_create() error");
        exit(3);
    }

    if (pthread_join(thid3, &ret) != 0) 
    {
        perror("pthread_create() error");
        exit(3);
    }

    printf("[%s]all thread exited delay:%d .\n", threadName, delay);
}

每個線程定義了兩個線程本地變量 threadName, delay,在線程處理函數(shù)中,對它們賦值后,再延遲一段時間,然后輸出這兩個變量值,結(jié)果可以看到每個線程的本地變量值都不一樣,可以獨立使用。

運行結(jié)果:

[senllang@hatch example_04]$ gcc -lpthread threadLocalStorage_gcc.c 
[senllang@hatch example_04]$ ./a.out 
[139945977145088] thread entered 
[139945960359680] thread entered 
[139945968752384] thread entered 
[139945960359680] thread 3 exiting after delay 12500.
[139945968752384] thread 2 exiting after delay 25000.
[139945977145088] thread 1 exiting after delay 50000.
[Main Thread]all thread exited delay:0 .

線程API方式

另一種使用線程本地變量的方式,是使用線程key相關(guān)的API,它分為兩類,一是創(chuàng)建和銷毀接口,另一類是變量的設(shè)置與獲取接口。

這種方式下,線程的本地數(shù)據(jù)存儲在 pthread結(jié)構(gòu)中,其中specific_1stblock,specific兩個數(shù)組按key值索引,并存儲對應(yīng)的線程本地數(shù)據(jù);

線程本地數(shù)據(jù)的數(shù)量,在這種方式下是有限的。

創(chuàng)建與銷毀接口

#include <pthread.h>  
int pthread_key_create(pthread_key_t *key, void(*destructor)(void*));
int pthread_key_delete(pthread_key_t key);

創(chuàng)建接口,獲取一個 pthread_key_t變量的值,其實就是內(nèi)存獲取一個鍵值來存儲數(shù)據(jù),第二個參數(shù)destructor傳遞一個銷毀數(shù)據(jù)的方法,當(dāng)本地數(shù)據(jù)為復(fù)雜數(shù)據(jù)類型,或者動態(tài)申請內(nèi)存時,在線程退出時進(jìn)行清理調(diào)用。

在線程使用完后,需要釋放對應(yīng)的key。

設(shè)置本地變量值接口

#include <pthread.h>  
int pthread_setspecific(pthread_key_t key, const void * value);
void * pthread_getspecific(pthread_key_t key);

這里設(shè)置線程的本地變量值,和獲取線程本地變量值;

在不同線程中設(shè)置時,就會只設(shè)置當(dāng)前線程的本地變量,不影響其它線程。

代碼示例

/* 
 * created by senllang 2024/1/1 
 * mail : study@senllang.onaliyun.com 
 * Copyright (C) 2023-2024, senllang
 */

#include <stdio.h>  
#include <pthread.h>  
  
// 定義一個 TLS 鍵  
pthread_key_t tls_key;  

void ShowThreadLocalData(char *prompt, pthread_t thid)
{
    // 獲取 TLS 存儲的值  
    int *value = (int *) pthread_getspecific(tls_key);  
    if (value == NULL) 
    {  
        printf("[%s]Thread: %ld, Value: NULL\n", prompt, thid);  
    } else 
    {  
        printf("[%s]Thread: %ld, Value: %d\n", prompt, thid, *value);  
    }  
}

// 線程函數(shù)  
void *thread_func(void *arg) 
{  
    ShowThreadLocalData("pre", pthread_self());

    pthread_setspecific(tls_key, (void *) arg);

    ShowThreadLocalData("after", pthread_self());
    return NULL;  
}  
  
int main() 
{  
    // 創(chuàng)建 2 個線程  
    pthread_t thread1, thread2;  
    int args1 = 100, args2=200;

    pthread_key_create(&tls_key, NULL); 

    // 設(shè)置 TLS 值  
    pthread_setspecific(tls_key, (void *) 500);  

    pthread_create(&thread1, NULL, thread_func, &args1);  
    pthread_create(&thread2, NULL, thread_func, &args2);  
  
    // 等待線程結(jié)束  
    pthread_join(thread1, NULL);  
    pthread_join(thread2, NULL);  

    pthread_key_delete(tls_key);
    return 0;  
}

在主線程和兩個子線程中都設(shè)置了本地變量值,運行后,可以看到每個線程中的值都不一樣。

[senllang@hatch example_04]$ gcc -lpthread threadLocalStorage_key.c 
[senllang@hatch example_04]$ ./a.out 
[pre]Thread: 140252914022144, Value: NULL
[after]Thread: 140252914022144, Value: 100
[pre]Thread: 140252905629440, Value: NULL
[after]Thread: 140252905629440, Value: 200

在線程開始時,獲取本地變量值,都沒有獲取到主線程設(shè)置的值。

兩種方式比較

  • 不同的存儲區(qū)域/尋址方法
    API 方式定義的數(shù)據(jù)由 specific_1stblock 數(shù)組和結(jié)構(gòu)的特定輔助數(shù)組尋址,而__thread存儲類型變量由??臻g地址偏移量尋址。

  • 性能/效率差異
    由于__thread由棧地址偏移量解決,因此性能高于 API方式。

  • 可以存儲不同的數(shù)據(jù)
    __thread只能修改常規(guī)的POD類型變量,對于指針類型數(shù)據(jù),動態(tài)申請的內(nèi)存,需要主動銷毀;而 API方式 支持傳入銷毀方法并支持所有數(shù)據(jù)類型。

  • 支持的數(shù)據(jù)數(shù)量不同
    理論上,只要堆棧不滿,__thread類型的變量就可以無限期定義;而API 方式只能創(chuàng)建PTHREAD_KEYS_MAX個鍵,但可以使用一個鍵通過結(jié)構(gòu)體等方式存儲多個值。

總結(jié)

本文所涉及的代碼已經(jīng)上傳到工程hatchCode, 在multipleThreads/example_04目錄下;

線程本地變量的使用,使得線程并發(fā)時,與進(jìn)程并發(fā)更加相似,都有自己的私有全局?jǐn)?shù)據(jù),當(dāng)然線程的特別之處在于,線程的本地變量的空間取決于線程棧的大小,當(dāng)然也可以是結(jié)構(gòu)指針,再動態(tài)申請空間,那么空間也就不存在問題了。

結(jié)尾

非常感謝大家的支持,在瀏覽的同時別忘了留下您寶貴的評論,如果覺得值得鼓勵,請點贊,收藏,我會更加努力!

作者郵箱:study@senllang.onaliyun.com
如有錯誤或者疏漏歡迎指出,互相學(xué)習(xí)。文章來源地址http://www.zghlxwxcb.cn/news/detail-778461.html

到了這里,關(guān)于【linux 多線程并發(fā)】線程本地數(shù)據(jù)存儲的兩種方式,每個線程可以有同名全局私有數(shù)據(jù),以及兩種方式的性能分析的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • 線程方法接收參數(shù)和返回參數(shù),Java的兩種線程實現(xiàn)方式對比

    總所周知,Java實現(xiàn)多線程有兩種方式,分別是繼承Thread類和實現(xiàn)Runable接口,那么它們的區(qū)別是什么? 繼承 Thread 類: 通過繼承 Thread 類,你可以創(chuàng)建一個直接表示線程的類。你可以覆蓋 Thread 類中的 run 方法來定義線程的邏輯。當(dāng)調(diào)用 start 方法啟動線程時,會執(zhí)行該類中的

    2024年02月11日
    瀏覽(29)
  • axios和vite在本地開發(fā)環(huán)境配置代理的兩種方式,五分鐘學(xué)會

    axios和vite在本地開發(fā)環(huán)境配置代理的兩種方式,五分鐘學(xué)會

    如果你使用vue或者react開發(fā),就得使用axios吧,然后為了解決跨域問題,就得使用vite配置吧,那怎么協(xié)調(diào)配置它們兩個才能正常工作呢? 正常的流程:配置axios的baseURL,然后配置vite的proxy 配置axios的baseURL: 然后再配置vite的proxy:在vite.config.js中配置 如果你想將路徑重寫也是

    2024年02月04日
    瀏覽(21)
  • IDEA中使用Tomcat的兩種方式:集成本地Tomcat&使用Tomcat Maven插件

    IDEA中使用Tomcat的兩種方式:集成本地Tomcat&使用Tomcat Maven插件

    在IDEA中創(chuàng)建完一個Maven Web項目,并補齊了目錄以后,準(zhǔn)備使用Tomcat時,就需要在自己創(chuàng)建的項目中去部署Tomcat,前文已經(jīng)介紹了如何創(chuàng)建Maven Web,所以這里就不多加贅述,直接講述部署Tomcat的方法 這種方法比較復(fù)雜,但是非常適用于tomcat7以上的高版本,且一定不會報錯 首先

    2023年04月08日
    瀏覽(25)
  • Linux 修改系統(tǒng)時間的兩種方式

    Linux 修改系統(tǒng)時間的兩種方式

    通過相關(guān)工具來手動修改系統(tǒng)的時間。 使用NTP自動同步系統(tǒng)時間。 作用:顯示和設(shè)置系統(tǒng)時間 選項: 時間格式: 例如:顯示年月日時分秒 只修改年月日: 只修改時分秒: 全部都改: 說明: 使用date命令修改的時間是 臨時生效 的,重啟系統(tǒng)后失效,需要將當(dāng)前時間和硬件

    2024年02月03日
    瀏覽(23)
  • Linux 修改網(wǎng)卡ip的兩種方式

    Linux 修改網(wǎng)卡ip的兩種方式

    實驗環(huán)境:WSL的ubuntu18.04 LTS 原本順序為131,132,133。執(zhí)行 ifconfig eth0 192.168.23.133 netmask 255.255.0.0 broadcast 192.168.23.255 后,提示“文件已存在”,因子網(wǎng)掩碼與最后一條不同產(chǎn)生錯續(xù)與錯改。 測試環(huán)境:Ubuntu18.04 Ubuntu18.04下 ip 命令報錯:RTNETLINK answers: no such process,且執(zhí)行: system

    2024年02月16日
    瀏覽(21)
  • Linux 修改系統(tǒng)時間的兩種方式?

    在Linux系統(tǒng)中,有兩種常見的方式可以修改系統(tǒng)時間:使用date命令和通過修改時區(qū)文件。 方式一:使用date命令 打開終端。 以root或具有管理員權(quán)限的用戶身份執(zhí)行以下命令來修改系統(tǒng)時間: 將\\\"YYYY-MM-DD HH:MM:SS\\\"替換為您想要設(shè)置的新時間。例如,要將系統(tǒng)時間設(shè)置為2023年6月

    2024年02月09日
    瀏覽(17)
  • 偷偷告訴你Linux 修改系統(tǒng)時間的兩種方式

    偷偷告訴你Linux 修改系統(tǒng)時間的兩種方式

    1、手動修改 通過相關(guān)工具來手動修改系統(tǒng)的時間。 2、自動同步 使用NTP自動同步系統(tǒng)時間。 1、date工具 作用:顯示和設(shè)置系統(tǒng)時間 選項: 時間格式: 例如:顯示年月日時分秒 只修改年月日: 只修改時分秒: 全部都改: 說明: 使用date命令修改的時間是 臨時生效 的,重

    2024年02月09日
    瀏覽(16)
  • Deepin/UOS Linux 配置普通用戶 sudo 權(quán)限 的兩種方式

    Deepin/UOS Linux 配置普通用戶 sudo 權(quán)限 的兩種方式 root 用戶下 配置 普通用戶 sudo 權(quán)限 echo \\\"zhangsan ALL=(ALL:ALL) NOPASSWD: ALL\\\" /etc/sudoers root 用戶下 配置 普通用戶 到 sudo 組下 vim /etc/sudoers 修改下面這一行 改為 然后修改 /etc/group 改為 也是闊以的 我們下期見,拜拜!

    2024年02月16日
    瀏覽(24)
  • Vue中強制更新數(shù)據(jù)的兩種方式

    有時候我們發(fā)現(xiàn)修改了數(shù)據(jù)源后視圖并沒有更新,這里提供兩種解決方案 Vue中強制更新數(shù)據(jù)的方法有兩種。 方法一: 使用forceUpdate強制渲染,更新視圖和數(shù)據(jù)。 注:全局強制刷新,性能消耗高。 方法二: this.$set()方法是Vue自帶的可對數(shù)組和對象進(jìn)行賦值,并觸發(fā)監(jiān)聽的方法

    2024年02月11日
    瀏覽(22)
  • Kafka使用MirrorMaker同步數(shù)據(jù)的兩種方式

    Kafka使用MirrorMaker同步數(shù)據(jù)的兩種方式

    MirrorMaker 是 Kafka官方提供的跨數(shù)據(jù)中心的 流數(shù)據(jù)同步方案 。原理是通過從 原始kafka集群 消費消息,然后把消息發(fā)送到 目標(biāo)kafka集群 。操作簡單,只要通過簡單的 consumer配置和 producer配置,然后啟動 Mirror,就可以實現(xiàn)準(zhǔn)實時的數(shù)據(jù)同步。 這里需要確保 目標(biāo)Kafka集群(接收數(shù)

    2023年04月14日
    瀏覽(25)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包