【问题标题】:Thread safety... what's my "best" course of action?线程安全......我的“最佳”行动方案是什么?
【发布时间】:2009-06-15 02:29:37
【问题描述】:

我想知道使数据线程安全的“最佳”方法是什么。

具体来说,我需要保护一个跨多个线程的链表——一个线程可能会尝试从中读取数据,而另一个线程会从其中添加/删除数据,甚至释放整个列表。我一直在阅读有关锁的文章;它们似乎是最常用的方法,但显然它们可能会出现问题(死锁)。我还阅读了有关原子操作以及线程本地存储的信息。

在您看来,我最好的做法是什么?大多数程序员使用什么方法,出于什么原因?

【问题讨论】:

  • 最佳方法取决于您使用的语言。例如,在 erlang 中,这不是问题
  • 是的,erlang 的 intrinsically 消息传递(所以多处理是其中最甜蜜的东西)——在我的回答中,我勾勒了如何用其他语言构建快乐状态,例如蟒蛇。

标签: multithreading locking atomic


【解决方案1】:

一种没有大量使用但相当合理的方法是指定一个专用线程来拥有每个“共享”结构。该线程通常等待(线程安全;-)队列,例如在 Python 中是 Queue.Queue 实例,用于工作请求(读取或更改共享结构),包括请求响应的请求(他们将传递自己的队列,在准备好后将响应放置在其中)和不请求的请求.这种方法完全序列化了对共享资源的所有访问,轻松地重新映射到多进程或分布式架构(在 Python 中几乎是无脑使用multiprocessing;-),并且绝对保证了健全性和没有死锁以及竞争条件只要底层队列对象一劳永逸地编程。

它基本上将共享数据结构的地狱变成了消息传递并发架构的天堂。

OTOH,它可能比用锁和c;-)艰难地猛击它的开销要高一点。

【讨论】:

  • 同意。我宁愿转向基于消息传递的接口,也不愿支持链表上的并发添加/删除和遍历。
  • 您仍然必须小心纯消息传递系统中的死锁...如果进程 A 在将消息 a 发送到进程 C 之前等待来自进程 B 的消息 b -AND- 进程B 只会在从进程 C 接收到消息 c 时发送消息 b -并且 - 进程 C 仅在接收到消息 a 时才发出消息 c。与死锁相同的基本问题仅取决于消息传递依赖关系而不是锁定资源。
  • 在 Windows API 中,这是 SendMessage(同步的,在发送被接收和处理之前阻塞发送方)和 PostMessage(异步)之间的区别。异步消息传递不会死锁(但您可能想知道您的帖子是否真的被接收和处理过)。
【解决方案2】:

你可以考虑一个不可变的集合。就像 .net 中的字符串如何具有 Replace、Insert 等方法一样。它不会修改字符串而是创建一个新字符串,LinkedList 集合也可以设计为不可变的。事实上,与其他一些集合数据结构相比,LinkedList 实际上以这种方式实现起来相当简单。

这是讨论不可变集合的博客文章的链接以及 .NET 中的一些实现的链接。

http://blogs.msdn.com/jaredpar/archive/2009/04/06/immutable-vs-mutable-collection-performance.aspx

【讨论】:

  • 这类数据结构通常被称为“持久性”数据结构(SO 上有一个persistent-data-structures 标签用于讨论这个问题)。这些通常可以与早期版本的数据结构共享内存单元,从而减少复制(以及内存使用,如果您保留数据结构的早期版本)。
【解决方案3】:

永远记住最重要的线程安全规则。彻底了解代码的所有关键部分。这样,就可以像了解您的 ABC 一样了解他们。只有当你在被问到时能够立即识别它们,你才会知道在哪些区域运行你的线程安全机制。

之后,记住经验法则:

  • 留意您所有的全球 堆上的变量/变量。
  • 确保您的子程序是 重入。
  • 确保对共享数据的访问权限 序列化。
  • 确保没有间接 通过指针访问。

(我相信其他人可以添加更多。)

【讨论】:

    【解决方案4】:

    从安全的角度来看,“最好”的方法是对整个数据结构加锁,这样一次只有一个线程可以访问它。

    一旦您决定锁定少于整个结构,大概是出于性能原因,这样做的细节是混乱的,并且对于每个数据结构,甚至相同结构的变体都不同。

    我的建议是

    1. 从对数据结构的全局锁定开始。分析您的程序,看看它是否真的有问题。

    2. 如果是问题,请考虑是否有其他方法可以解决问题。你能最小化相关数据结构中的数据量,从而不需要如此频繁或长时间地访问它吗?例如,如果它是一个队列系统,也许您可​​以为每个线程保留一个本地队列,并且仅当本地队列过载或不足时才将事物移入或移出全局队列。

    3. 查看旨在帮助减少对您正在做的特定类型的事情的争用的数据结构,并仔细而准确地实施它们,以免在安全方面犯错。对于队列示例,工作窃取队列可能是您所需要的。

    【讨论】:

    • 这种方法还假设“整个结构”是自包含的,并且您一次不需要锁定多个对象。通常,当一个结构中的数据指向另一个结构中的数据时,结构锁的全局问题就会发生。然后死锁就成了家常便饭。
    • 确实,如果您需要锁定多个结构并且您使用多个锁,而不是一个锁来一次锁定所有结构,那么您处于相同的位置,就好像您使用多个锁来锁定不同的数据结构的一部分。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-17
    • 1970-01-01
    • 2017-03-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多