簡體   English   中英

如何使 C 包裝器 API 線程安全?

[英]How to make a C wrapper API thread safe?

編輯:我們使用的是 C99。

首先,一些上下文設置/信息。 I'm writing an API in C that wraps around a C checksum API that utilizes IUF (Initialize(), Update(), Finalize()) functions.

我的 API 包含一個 function,我們稱之為 foo(),它以輸入結構指針作為參數。 它的編寫方式使得被散列的數據可以以塊/塊的形式提供,而不是整個數據緩沖區。 此代碼將在資源非常有限的嵌入式系統上運行,因此能夠將文件分塊讀取到 hash 數據是此 API 的特定目的。 使用單個 function 不是我的選擇,而是一個要求。

int foo(struct_t*);

struct_t 看起來像這樣。

{
   char* data,
   int dataLen,
   bool finalBlock,
   char checksum[CHECKSUM_SIZE]
}

基本上,每次調用 function 時,foo() 的調用者都會用下一個數據塊和該數據的大小填充“data”和“dataLen”參數,直到數據完全提供給 foo()。 不幸的是,'finalBlock' 參數是告訴 foo() 您正在提供要處理的最后一塊數據的唯一方法。 對於用例,這是可以理解的並且可以的。

現在到了真正的問題。 內部 IUF 校驗和 API 具有自己的獨特數據結構,我不允許將其暴露給 foo() 的調用者。 由於我們現在只在一個線程中使用 foo(),因此當前的解決方案是使 IUF API 的結構(我們將其稱為 bar)成為 static 變量。 這使得 foo() function 不是線程安全的。

int foo(struct_t* x)
{
   /*Struct required by IUF API*/
   static bar y = {0};
   int retCode = 0;
   /*rest of code that processes data as needed*/
   ...
   /*after finalizing checksum and storing in x, reset y*/
   return retCode;
}

如果可能的話,我想讓 foo() 線程安全,而不會將“bar”結構暴露給 foo() 的調用者。 有什么建議么?

TL;DR:擁有一個 API 和一個 function,需要多次調用才能完成工作。 必須向此 API 的調用者隱藏內部結構,但該結構必須持續存在,直到 API 完全完成。 目前將該結構設置為 static 變量,因此它會一直存在直到 API 完成,但這使我的 API 不是線程安全的。 請幫忙。

有兩種基本的結構方式:

  1. 一次向一個線程授予對整個子系統的獨占訪問權限。 您可以借助互斥鎖和條件變量來實現這一點,如果您願意,可以是foo()的 static 成員。 這對調用者來說是不可見的,但它阻止了所有並發,即使在多塊情況下的foo()調用之間也是如此。

  2. 給每個線程自己的數據。 如果您無法適應foo()的非並發性,這就是您想要的。

即使無法訪問 C11 的內置線程本地數據支持,您使用的任何線程 API 都可能具有 TLD 機制或實質上的等效機制。 特別是, pthreads 確實如此,如果這恰好是您正在使用的。 您甚至可以在 hash 表的幫助下自己動手。

但是由於您正在為資源受限的系統編寫代碼,因此您可能對輕量級方法感興趣,並且絕對最輕量級的方法是讓每個線程通過為struct_t提供一個成員bar來提供自己的數據。 這不僅避免了訪問每個線程數據時涉及的任何類型的間接或查找,而且還可以避免任何動態 memory 分配。 但這確實將bar暴露給foo的調用者,你說你不能這樣做。

在這兩者之間,您可以選擇讓foo分配bar s,並將它們不透明地交給調用者,期望它們在每個關聯的調用中返回。 這可能最適合您的需求,這是我將重點關注此答案的 rest 的地方。

要實現這種方法,首先要給struct_t一個額外的成員來存儲上下文數據:

struct struct_t {
    char* data;
    int dataLen;
    bool finalBlock;
    char checksum[CHECKSUM_SIZE];
    void *state;  // <-- this
};

調用者state設置為 null 指針,用於序列的初始調用(這是foo可以識別的方式,我沒有看到其他規定),並在隨后的調用中傳回foo提供的任何值在里面。 因此,自然調用模式看起來像這樣:

    struct struct_t checksum_state = { 0 };
    int result;

    checksum_state.data = some_data;
    checksum_state.dataLen = the_length;
    result = foo(&checksum_state);
    // ... check / handle result ...

    checksum_state.data = more_data;
    checksum_state.dataLen = new_length;
    result = foo(&checksum_state);
    // ... check / handle result ...

    checksum_state.data = last_data;
    checksum_state.dataLen = last_length;
    checksum_state.finalBlock = 1;
    result = foo(&checksum_state);
    // ... check / handle result ...
    // ... use checksum_state.checksum ...

我認為這與您已經擁有的使用模式有關,並且我注意到它根本不需要調用者顯式使用或確認checksum_state.state ,盡管這部分是由調用者使用初始化程序而不是 per -成員初始化。

foo()方面,它的結構如下:

int foo(struct_t* x) {
    int retCode = 0;
    struct bar *y;

    if (!x->state) {
        x->state = calloc(1, sizeof(struct bar));
        if (!x->state) // ... handle allocation error ...
    }
    y = x->state;

    /* Do stuff with x and y */

    if (x->lastBlock) {
        free(x->state);
    }

    return retCode;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM