簡體   English   中英

如何在共享內存的同一區域上工作的兩個進程之間共享鎖?

[英]How do I share locks between two processes working on the same region of shared memory?

我想知道如何做到這一點(使用C ++ 98)。 這是我的場景:我有進程A和進程B。進程A在共享內存中分配一個大緩沖區,並將其拆分為固定數量的塊。 然后,它使用一系列類似的結構來表示每個塊:

struct Chunk
{
    Lock lock; //wrapper for pthread_attr_t and pthread_mutex_t
    char* offset; //address of the beginning of this chunk in the shared memory buffer
};

構造鎖時,它會執行以下操作:

pthread_mutexattr_init(&attrs);
pthread_mutexattr_setpshared(&attrs, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(&lock,&attrs); //lock is pthread_mutex_t and attrs is pthread_mutexattr_t

調用lock方法時會執行以下操作:

pthread_mutex_lock(&lock);

在共享內存緩沖區的開頭創建上述“塊”時,它將使用new放置,如下所示:

char* mem; //pointer to the shared memory
Chunks[i] = new (mem) Chunk; //for i = {1..num chunks}
mem += sizeof(Chunk);

然后,它分配偏移量,並在其生命周期內繼續寫入緩沖區的其余部分。 每次它寫入與上述其中之一相對應的塊時,它都會抓住塊鎖並在完成后釋放它。

現在,進程B出現並將相同的共享內存緩沖區映射到內存中,並嘗試像這樣檢索塊:

Chunk** chunks = reinterpret_cast<Chunk**)(mem); //mem being the pointer into the shared memory

然后它嘗試通過掃描不同的塊並在需要時嘗試利用鎖來對共享內存進行操作。

當我在其中運行大塊**的垃圾時,我遇到了奇怪的崩潰,並且想知道Lock是否也可以在整個進程中正常工作,或者在上述簡單步驟中是否有我忽略的其他警告? 擁有共享的pthread attr是否足夠?還是需要使用一種完全不同的鎖?

當您將共享內存區域拉入進程時,通常不會將其與訪問共享內存的其他進程位於相同的虛擬地址。 因此,您不能只將原始指針存儲到共享內存中並期望它們有意義地工作。

因此,在您的情況下,即使Chunk位於共享內存中,每個塊內的offset指針在任何其他進程中也沒有意義。

一種解決方案是使用共享內存塊開頭的偏移量

struct Chunk {
    pthread_mutex_t  lock;
    size_t           offset;
};

char *base; // base address of shared memory
char *mem;  // end of in-use shared memory

Chunk *chunks = reinterpret_cast<Chunk *>(mem);  // pointer to array in shared memory
for (int i = 0; i < num_chunks; i++) {
    // initialize the chunks
    new(mem) Chunk;
    mem += sizeof(Chunk); }
// set the offsets to point at some memory
for (int i = 0; i < num_chunks; i++) {
    chunks[i].offset = mem - base;
    mem += CHUNK_SIZE; // how much memory to allocate for each chunk?
}

現在在B中,您可以

Chunk *chunks = reinterpret_cast<Chunk *>(base);

但是在任一過程中,要訪問塊的數據,您都需要base + chunks[i].offset

或者,您可以使用一個包來管理您的共享內存分配,並確保在每個進程中將其映射到相同的地址

除了我的評論外,我還提供了一個非常基本的offset_ptr實現。 我同意,使一個簡單的項目依賴於諸如boost之類的東西可能是一個過大的殺傷力,但是即使在達到某個特定項目規模之前,您決定切換到更嚴格的庫集,也值得包裝諸如偏移指針之類的關鍵內容。 包裝可以幫助您集中化偏移量處理程序代碼,並使用斷言保護自己。 一個不能處理所有情況的簡單offset_ptr模板仍然比手動編碼的offset + pointer操縱器代碼要好得多,后者可以粘貼到各處,並且其基本實現無需嘗試全面:

template <typename T, typename OffsetInt=ptrdiff_t>
class offset_ptr
{
    template <typename U, typename OI> friend class offset_ptr;
public:
    offset_ptr() : m_Offset(0) {}
    offset_ptr(T* p)
    {
        set_ptr(p);
    }
    offset_ptr(offset_ptr& other)
    {
        set_ptr(other.get_ptr());
    }
    template <typename U, typename OI>
    offset_ptr(offset_ptr<U,OI>& other)
    {
        set_ptr(static_cast<T*>(other.get_ptr()));
    }
    offset_ptr& operator=(T* p)
    {
        set_ptr(p);
        return *this;
    }
    offset_ptr& operator=(offset_ptr& other)
    {
        set_ptr(other.get_ptr());
        return *this;
    }
    template <typename U, typename OI>
    offset_ptr& operator=(offset_ptr<U,OI>& other)
    {
        set_ptr(static_cast<T*>(other.get_ptr()));
        return *this;
    }
    T* operator->()
    {
        assert(m_Offset);
        return get_ptr();
    }
    const T* operator->() const
    {
        assert(m_Offset);
        return get_ptr();
    }
    T& operator*()
    {
        assert(m_Offset);
        return *get_ptr();
    }
    const T& operator*() const
    {
        assert(m_Offset);
        return *get_ptr();
    }
    operator T* ()
    {
        return get_ptr();
    }
    operator const T* () const
    {
        return get_ptr();
    }

private:
    void set_ptr(const T* p)
    {
        m_Offset = p ? OffsetInt((char*)p - (char*)this) : OffsetInt(0);
    }
    T* get_ptr() const
    {
        return m_Offset ? (T*)((char*)this + m_Offset) : (T*)nullptr;
    }

private:
    OffsetInt m_Offset;
};

offset_ptr<int> p;
int x = 5;

struct TestStruct
{
    int member;
    void func()
    {
        printf("%s(%d)\n", __FUNCTION__, member);
    }
};

TestStruct ts;
offset_ptr<TestStruct> pts;

int main()
{
    p = &x;
    *p = 6;
    printf("%d\n", x);

    ts.member = 11;
    if (!pts)
        printf("pts is null\n");
    pts = &ts;
    if (pts)
        pts->func();
    pts = nullptr;
    if (!pts)
        printf("pts is null again\n");

    // this will cause an assert because pts is null
    pts->func();
    return 0;
}

它可能包含一些操作員函數,如果您不習慣於實現指針的話,會很費勁,但是與像boost這樣的復雜lib的完整指針實現相比,這真的很簡單,不是嗎? 而且它使指針(偏移)操縱器代碼變得更好! 沒有任何理由不使用至少一個這樣的包裝器!

即使沒有外部庫就自己做,也可以包裝鎖和其他東西,因為在使用裸本機api編寫/使用鎖2至3-4次后,它會斷掉,如果您使用,代碼看起來會更好一個帶有ctor / destructor / lock()/ unlock()的簡單包裝器,更不用說可以由斷言保證的集中式鎖定/解鎖代碼,有時您可以將調試信息放入鎖定類(例如最后一個的id)鎖線程更容易調試死鎖...)。

因此,即使您在沒有std或boost的情況下滾動代碼,也不要忽略包裝器。

暫無
暫無

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

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