Boost interprocess_condition 多个线程调用 wait() 失败
Boost interprocess_condition multiple threads calling wait() fails
运行 变成了一个非常奇怪的问题,有 2 个以上的线程在等待 interprocess_condition 变量。
提升 1.60.0
- 1 个线程调用 wait() 和第 2 个线程调用 notify_all(),一切都按预期工作。
- 当有 2+ 次调用 wait() 时,我在 do_wait() 上遇到断言失败并且进程退出。
Test.cpp:
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/sync/interprocess_mutex.hpp>
#include <boost/interprocess/sync/interprocess_condition.hpp>
#include <iostream>
using namespace boost::interprocess;
struct Data {
interprocess_mutex mux_;
interprocess_condition cond_;
};
int main(int argc, char *argv[]) {
if (argc > 1 && atoi(argv[1]) == 0) {
struct shm_remove {
shm_remove() { shared_memory_object::remove("MySharedMemory"); }
~shm_remove() { shared_memory_object::remove("MySharedMemory"); }
} remover;
managed_shared_memory seg(create_only, "MySharedMemory", 65536);
Data *const d = seg.construct<Data>(unique_instance)();
scoped_lock<interprocess_mutex> lock(d->mux_);
std::cout << "Waiting" << std::endl;
d->cond_.wait(lock);
} else if (argc > 1 && atoi(argv[1]) == 1) {
managed_shared_memory seg(open_only, "MySharedMemory");
std::pair<Data *, std::size_t> res = seg.find<Data>(unique_instance);
scoped_lock<interprocess_mutex> lock(res.first->mux_);
std::cout << "Waiting" << std::endl;
res.first->cond_.wait(lock);
} else {
managed_shared_memory seg(open_only, "MySharedMemory");
std::pair<Data *, std::size_t> res = seg.find<Data>(unique_instance);
scoped_lock<interprocess_mutex> lock(res.first->mux_);
std::cout << "Notifying" << std::endl;
res.first->cond_.notify_all();
}
}
编译为:
$ clang++ -I/usr/local/include test.cpp
运行 1 wait() 和 1 notify():
$ ./a.out 0&
[8] 25889
Waiting
$ ./a.out 2&
[9] 25901
Notifying
[8]- Done ./a.out 0
[9]+ Done ./a.out 2
运行 2 次等待:
$ ./a.out 0&
[8] 25986
Waiting
$ ./a.out 1&
[9] 25998
Waiting
Assertion failed: (res == 0), function do_wait, file /usr/local/include/boost/interprocess/sync/posix/condition.hpp, line 175.
在 OSX El Capitan
上测试
$ uname -a
Darwin LUS-JOHUGHES2 15.3.0 Darwin Kernel Version 15.3.0: Thu Dec 10 18:40:58 PST 2015; root:xnu-3248.30.4~1/RELEASE_X86_64 x86_64
我还在 Ubuntu Trusty 机器上尝试了上面的示例,所有示例都按预期工作,这让我相信 OSX 实施存在问题。我还没有在 Windows.
上试过
进行了一些挖掘并找到了问题的明确答案。
当第二个进程调用 do_wait() 时,上面的 boost 断言错误失败,后者调用 pthread_wait() returns 立即使用 EINVAL(而不是成功的 0).
在OSX的pthread实现中,条件变量存储指向互斥量变量的原始指针。在第一个进程中对 pthread_wait() 的第一次调用设置了这个指针。对 pthread_wait() 的第二次调用根据传递给 pthread_wait() 的互斥指针检查这个存储的互斥指针。 {可以在此处的源代码中找到:https://opensource.apple.com/source/libpthread/libpthread-137.1.1/src/pthread_cond.c}
由于两个进程在不同的地址空间映射了共享的互斥锁和条件变量,所以第二次调用 pthread_wait() 将永远不会工作,因为它比较原始指针。
因此,使这项工作可行的 2 个选项如下:
使用固定地址映射:http://www.boost.org/doc/libs/1_60_0/doc/html/interprocess/sharedmemorybetweenprocesses.html#interprocess.sharedmemorybetweenprocesses.mapped_region.mapped_region_fixed_address_mapping保证映射区域在同一地址,因此原始指针可以工作,或者
使用 fork() 而不是 exec() 的新进程,这意味着子进程将拥有原始段管理器的副本,映射到相同的地址,因此是原始指针将工作。
我没有深入研究 glibc pthreads 代码以了解它们与 Apple 有什么不同,所以我不确定为什么原始示例适用于 Linux 而不是 OSX。
我认为 Boost 文档肯定会受益于一段讨论这个陷阱的段落。
这是 Darwin 的 C 库和 Boost.Interprocess 上的错误。首先,该 C 库声称它符合 posix 并支持进程共享内存条件变量,这是错误的(因为它使用原始指针来存储互斥锁的地址)。其次,Boost.Interprocess 应该将此平台检测为有问题的平台,应该禁用 pthreads 的使用并回退到仿真。
在 boost/interprocess/detail/workaround.hpp 中,您会发现一条评论说:
//Mac Os X < Lion (10.7) might define _POSIX_THREAD_PROCESS_SHARED but there is no real support.
一些旧报告声称较新的 macos 版本确实支持进程共享条件变量,但这种说法是错误的,因此 __APPLE__
部分应该只是:
#define BOOST_INTERPROCESS_BUGGY_POSIX_PROCESS_SHARED
运行 变成了一个非常奇怪的问题,有 2 个以上的线程在等待 interprocess_condition 变量。
提升 1.60.0
- 1 个线程调用 wait() 和第 2 个线程调用 notify_all(),一切都按预期工作。
- 当有 2+ 次调用 wait() 时,我在 do_wait() 上遇到断言失败并且进程退出。
Test.cpp:
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/sync/interprocess_mutex.hpp>
#include <boost/interprocess/sync/interprocess_condition.hpp>
#include <iostream>
using namespace boost::interprocess;
struct Data {
interprocess_mutex mux_;
interprocess_condition cond_;
};
int main(int argc, char *argv[]) {
if (argc > 1 && atoi(argv[1]) == 0) {
struct shm_remove {
shm_remove() { shared_memory_object::remove("MySharedMemory"); }
~shm_remove() { shared_memory_object::remove("MySharedMemory"); }
} remover;
managed_shared_memory seg(create_only, "MySharedMemory", 65536);
Data *const d = seg.construct<Data>(unique_instance)();
scoped_lock<interprocess_mutex> lock(d->mux_);
std::cout << "Waiting" << std::endl;
d->cond_.wait(lock);
} else if (argc > 1 && atoi(argv[1]) == 1) {
managed_shared_memory seg(open_only, "MySharedMemory");
std::pair<Data *, std::size_t> res = seg.find<Data>(unique_instance);
scoped_lock<interprocess_mutex> lock(res.first->mux_);
std::cout << "Waiting" << std::endl;
res.first->cond_.wait(lock);
} else {
managed_shared_memory seg(open_only, "MySharedMemory");
std::pair<Data *, std::size_t> res = seg.find<Data>(unique_instance);
scoped_lock<interprocess_mutex> lock(res.first->mux_);
std::cout << "Notifying" << std::endl;
res.first->cond_.notify_all();
}
}
编译为:
$ clang++ -I/usr/local/include test.cpp
运行 1 wait() 和 1 notify():
$ ./a.out 0&
[8] 25889
Waiting
$ ./a.out 2&
[9] 25901
Notifying
[8]- Done ./a.out 0
[9]+ Done ./a.out 2
运行 2 次等待:
$ ./a.out 0&
[8] 25986
Waiting
$ ./a.out 1&
[9] 25998
Waiting
Assertion failed: (res == 0), function do_wait, file /usr/local/include/boost/interprocess/sync/posix/condition.hpp, line 175.
在 OSX El Capitan
上测试$ uname -a
Darwin LUS-JOHUGHES2 15.3.0 Darwin Kernel Version 15.3.0: Thu Dec 10 18:40:58 PST 2015; root:xnu-3248.30.4~1/RELEASE_X86_64 x86_64
我还在 Ubuntu Trusty 机器上尝试了上面的示例,所有示例都按预期工作,这让我相信 OSX 实施存在问题。我还没有在 Windows.
上试过进行了一些挖掘并找到了问题的明确答案。
当第二个进程调用 do_wait() 时,上面的 boost 断言错误失败,后者调用 pthread_wait() returns 立即使用 EINVAL(而不是成功的 0).
在OSX的pthread实现中,条件变量存储指向互斥量变量的原始指针。在第一个进程中对 pthread_wait() 的第一次调用设置了这个指针。对 pthread_wait() 的第二次调用根据传递给 pthread_wait() 的互斥指针检查这个存储的互斥指针。 {可以在此处的源代码中找到:https://opensource.apple.com/source/libpthread/libpthread-137.1.1/src/pthread_cond.c}
由于两个进程在不同的地址空间映射了共享的互斥锁和条件变量,所以第二次调用 pthread_wait() 将永远不会工作,因为它比较原始指针。
因此,使这项工作可行的 2 个选项如下:
使用固定地址映射:http://www.boost.org/doc/libs/1_60_0/doc/html/interprocess/sharedmemorybetweenprocesses.html#interprocess.sharedmemorybetweenprocesses.mapped_region.mapped_region_fixed_address_mapping保证映射区域在同一地址,因此原始指针可以工作,或者
使用 fork() 而不是 exec() 的新进程,这意味着子进程将拥有原始段管理器的副本,映射到相同的地址,因此是原始指针将工作。
我没有深入研究 glibc pthreads 代码以了解它们与 Apple 有什么不同,所以我不确定为什么原始示例适用于 Linux 而不是 OSX。
我认为 Boost 文档肯定会受益于一段讨论这个陷阱的段落。
这是 Darwin 的 C 库和 Boost.Interprocess 上的错误。首先,该 C 库声称它符合 posix 并支持进程共享内存条件变量,这是错误的(因为它使用原始指针来存储互斥锁的地址)。其次,Boost.Interprocess 应该将此平台检测为有问题的平台,应该禁用 pthreads 的使用并回退到仿真。
在 boost/interprocess/detail/workaround.hpp 中,您会发现一条评论说:
//Mac Os X < Lion (10.7) might define _POSIX_THREAD_PROCESS_SHARED but there is no real support.
一些旧报告声称较新的 macos 版本确实支持进程共享条件变量,但这种说法是错误的,因此 __APPLE__
部分应该只是:
#define BOOST_INTERPROCESS_BUGGY_POSIX_PROCESS_SHARED