C++11进程间原子操作和互斥锁

22

我有一个Linux程序,它会生成多个进程(fork),并通过POSIX共享内存进行通信。我希望每个进程都分配一个id(0-255)。我的想法是在共享内存区域中放置一个位向量(初始化为零),并原子地比较和交换一个位来分配id。

是否有c++11友好的方法来实现这一点?我可以创建一个原子位集吗?我可以跨进程使用互斥锁吗?如何确保构造函数在所有进程中只调用一次?


1
@aleguna 因为我想要一个值的范围在0-255之间,当一个进程离开这个程序时,它应该释放它的ID以便被重用。 - dschatz
2
@NicolBolas 我将我的问题简化,并选择了最少的必要信息来提出一个问题,以便解决我的问题。我不是在寻求别人改变问题本身。我可以创建一个任意的同步问题,并就进程间的 POSIX 共享内存提出问题,这个问题仍然适用。如果有帮助的话,可以假设我问的是关于共享数据结构以及如何使用 C++ 互斥锁或原子操作与之相关的问题。 - dschatz
1
@dschatz:因此,您遇到了一个典型的XY问题。 - Nicol Bolas
2
@dschatz:“我想了解C++11互斥锁和原子操作在进程间的工作方式(嘿,标题很匹配!)”然后,您不应该在一般性问题的第一段中给出非常具体、人为且无用的示例。如果您将其陈述为“我想知道C++11线程原语在进程间的工作方式。例如,blah”,那么问题就会变得清晰明了。一个通用的标题和一个具体的内部问题在SO上很常见。以至于我一旦开始阅读问题,就会忽略标题。 - Nicol Bolas
5
C++11没有跨进程函数。Boost::Interprocess可能会为您提供一些工具,用于解决这个问题。 - Christopher
显示剩余7条评论
2个回答

19
C++11的线程原语(互斥、原子等)是线程原语。C++标准没有涉及进程,这些工具中大多数在进程间不能相互操作。
标准中关于进程唯一的提及是在一个非规范符号中,它说无锁原子应该适用于IPC:

无锁操作也应当是无地址操作的。也就是说,对于同一内存位置进行的两个不同地址的原子操作将以原子方式通信。实现不应依赖任何进程状态。此限制使得可以通过被映射到一个进程中多次或者被共享在两个进程之间的内存来进行通信。

除了这个非规范的注释之外,线程原语并不意味着要实现进程间通信的手段。在共享内存中放置这些对象的行为(除了如上所述的无锁原子之外)是未定义的。

14
[atomics.lockfree]中包含以下内容:“[注:具有无锁特性的操作也应该是无地址依赖的。也就是说,通过两个不同地址对同一内存位置进行原子操作将会产生原子通信。实现不应依赖于任何进程级别的状态。这一限制使得可以通过被映射到一个进程中多次的内存或者被两个进程共享的内存进行通信。— 结束注释]”。 - Jeffrey Yasskin
1
亲爱的@Nicol Bolas,您的回答非常好,但我无法找到任何地方或任何书籍关于在共享内存中使用C++11互斥锁的写作,所以我仍然对此感到困惑。(据我所知,如果使用PTHREAD_PROCESS_SHARED,则pthread互斥锁是可以的) - Trung Nguyen
这个回答完全是错误的。Jefrrey Yasskin在他的评论中已经指出了一个原因(std::atomic实际上非常适合共享内存IPC,有时候是必要的),但他的评论很容易被忽略。互斥锁也可以用于共享内存IPC,尽管标准库的互斥锁并不是为这样的使用而设计的。 - user2373145
2
而且,需要明确的是,标准的意图显然是原子操作应该可用于进程间通信。杰弗里在标准中的引用表明这一点是正确的。话虽如此,我不知道这是否已经在C++11标准中了。 - user2373145
@user2373145说:“标准的意图显然是原子操作应该可用于进程间通信。”如果这是“明确”的,为什么不是实际文本而是注释呢?标准文件中的规范文本从未提到过进程。有一个关于无锁(lock-free)的“地址自由”(address free)的注释,但C++内存和对象模型本身并没有承认这样的概念。 - Nicol Bolas
显示剩余2条评论

10

您可以在共享内存块内使用互斥锁,但互斥锁必须声明为SHARED。因此,在共享内存中使用互斥锁并不罕见,您可以创建自己的类来实现,这非常简单:

class Mutex {
private:
    void *_handle;
public:
    Mutex(void *shmMemMutex,  bool recursive =false, );
    virtual ~Mutex();

    void lock();
    void unlock();
    bool tryLock();
};

Mutex::Mutex(void *shmMemMutex, bool recursive)
{
    _handle = shmMemMutex;
    pthread_mutexattr_t attr;
    ::pthread_mutexattr_init(&attr);
    ::pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
    ::pthread_mutexattr_settype(&attr, recursive ? PTHREAD_MUTEX_RECURSIVE_NP : PTHREAD_MUTEX_FAST_NP);

    if (::pthread_mutex_init((pthread_mutex_t*)_handle, &attr) == -1) {
        ::free(_handle);
        throw ThreadException("Unable to create mutex");
    }
}
Mutex::~Mutex()
{
    ::pthread_mutex_destroy((pthread_mutex_t*)_handle);
}
void Mutex::lock()
{
    if (::pthread_mutex_lock((pthread_mutex_t*)_handle) != 0) {
        throw ThreadException("Unable to lock mutex");
    }
}
void Mutex::unlock()
{
    if (::pthread_mutex_unlock((pthread_mutex_t*)_handle) != 0) {
        throw ThreadException("Unable to unlock mutex");
    }
}
bool Mutex::tryLock()
{
    int tryResult = ::pthread_mutex_trylock((pthread_mutex_t*)_handle);
    if (tryResult != 0) {
        if (EBUSY == tryResult) return false;
        throw ThreadException("Unable to lock mutex");
    }
    return true;
}

2
这个真的需要一个virtual析构函数吗?另外,最好使用pthread_mutex_t *handle而不是每次使用时进行强制转换。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接