如何创建线程安全的缓冲区/POD?

14

我的问题可能很常见,但它让我发疯:

我有一个多线程应用程序,其中有5个线程。其中4个线程执行它们的任务,例如网络通信和本地文件系统访问,然后将它们的输出都写入该数据结构中:

struct Buffer {
  std::vector<std::string> lines;
  bool has_been_modified;
}
第5个线程将这些缓冲区/结构打印到屏幕上。
Buffer buf1, buf2, buf3, buf4;

...

if ( buf1.has_been_modified || 
     buf2.has_been_modified || 
     buf3.has_been_modified || 
     buf4.has_been_modified )
{
  redraw_screen_from_buffers();
}

我如何在缓冲区正在进行读写操作时保护它们不被覆盖?

虽然我认为这应该是一个很普遍的问题,但我找不到合适的解决方案。

谢谢。


1
使用互斥锁 - Diego
谢谢您的回复。我在多线程方面经验不是很丰富。能否给我一个简短的例子? - dummy
你询问POD的原因是什么?(请注意,目前被接受的答案并没有描述POD) - user1084944
是的,我不想围绕我的“struct”构建一个大类,并添加getter和setter。而且,称我的“struct”为缓冲区对我来说似乎并不完全正确,因此我添加了POD。 - dummy
4个回答

11

你应该使用互斥锁。互斥锁类是std::mutex。在C++11中,您可以使用std::lock_guard<std::mutex>来封装互斥锁,使用RAII。因此,您需要将Buffer结构体更改为

struct Buffer {
   std::vector<std::string> lines;
   bool has_been_modified;
   std::mutex mutex;
};

每当您读取或写入缓冲区或 has_been_modified 时,都需要执行以下操作:

std::lock_guard<std::mutex> lockGuard(Buffer.mutex); //Do this for each buffer you want to access
... //Access buffer here

当使用lock_guard时,互斥锁会在它被销毁时自动释放。

你可以在这里了解更多关于互斥锁的内容。


谢谢!这正是我需要的! - dummy
1
@user4832939 - 虽然你明确要求的是互斥锁,但你所描述的情况似乎是条件变量的有效用例。请查看Schultz9999的答案以获取详细信息。 - Daniel Kamil Kozar
读取或修改缓冲区,或者也要修改 has_been_modified 布尔值。不仅仅是修改。(更具体地说,如果有可能在读取时有其他线程正在写入,那么还需要在读取周围进行锁定) 查找“竞争条件”的定义。 - Andre Kostur
@AndreCostur 发现得好!已修复。 - phantom

5

你可以在缓冲区周围使用互斥锁(或互斥量),以确保它们不会被多个线程同时修改。

// Mutex shared between the multiple threads
std::mutex g_BufferMutex;

void redraw_screen_from_buffers()
{
   std::lock_guard<std::mutex> bufferLockGuard(g_BufferMutex);
   //redraw here after mutex has been locked.
}

那么您的缓冲修改代码在修改缓冲时也必须锁定相同的互斥对象。

void updateBuffer()
{
   std::lock_guard<std::mutex> bufferLockGuard(g_BufferMutex);
   // update here after mutex has been locked
}

这里 包含了一些互斥锁的示例。


糟糕,这意味着一次只能修改一个缓冲区。 - Krab
@Krab 然后每个缓冲区使用一个互斥锁,redraw_screen_from_buffers 函数在重新绘制之前会锁定所有的互斥锁。 - lcs

3

2

当我们试图避免数据竞争时,互斥锁是一种非常好的工具。我相信@Phantom发布的答案能够满足大多数人的需求。但是,需要知道的是,这种方法在大型系统中并不可扩展。

通过加锁,您可以同步线程。由于只有一个线程可以访问向量,一个线程写入容器将导致另一个线程等待它完成...这对您可能是好事,但在需要高性能时会导致严重的性能瓶颈。

最好的解决方案是使用更复杂的无锁结构。不幸的是,我认为STL中没有任何标准的无锁结构。一个无锁队列的例子可以在这里找到。

使用这样的结构,您的4个工作线程可以将消息排队到容器中,而第5个线程将取出它们,而不会发生任何数据竞争。

在此处了解更多关于无锁数据结构的内容!


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