繁体   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