我想知道如何做到这一点(使用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
调用时锁定方法执行此操作:
pthread_mutex_lock(&lock);
在上面创建上面的" Chunk" s时,它会使用placement 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是否也可以在整个过程中工作,或者是否有任何其他警告我只是在简单的步骤中忽略以上?是否有足够的SHARED pthread attr或者我是否需要使用完全不同的锁?
答案 0 :(得分:0)
当您将共享内存区域拉入进程时,它通常不会与访问共享内存的其他进程位于同一虚拟地址。因此,您不能只将原始指针存储到共享内存中,并希望它们能够有效地工作。
因此,在您的情况下,即使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
答案 1 :(得分:0)
除了我的评论,我还提供了一个非常基本的offset_ptr实现。我同意将一个简单的项目依赖于像boost这样的东西可能是一种矫枉过正,但即使达到某个项目大小,你决定切换到一个更严肃的库集合,也值得包装像偏移指针这样的关键事物。环绕可以帮助您集中偏移处理程序代码,也可以使用断言来保护自己。一个简单的offset_ptr模板不能处理所有情况仍然比手工编码的偏移量+指针操纵器代码要好得多,这些代码可以在任何地方进行复制粘贴,而且它的基本实现不会尝试全面:
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的情况下滚动代码,也不要省略包装。