用于同步的锁的替代方案

时间:2010-11-26 04:34:57

标签: c++ multithreading locking

我目前正在开发自己的小线程库,主要用于学习目的,并且处于消息队列的一部分,这将涉及在各个地方进行大量同步。以前我主要使用锁,互斥和条件变量,它们都是同一主题的变体,一个锁的一个部分只能由一个线程一次使用。

与使用锁相比,是否有任何不同的同步解决方案?我已经在地方读过无锁同步,但有些人认为将容器中的锁隐藏起来是无锁的,我不同意这一点。你自己没有明确地使用锁。

4 个答案:

答案 0 :(得分:13)

无锁算法通常涉及使用比较和交换(CAS)或类似的CPU指令,这些指令不仅在原子上更新内存中的某些值,而且还有条件地并且具有成功指示。这样你可以编写如下代码:

1 do
2 {
3     current_value = the_varibale
4     new_value = ...some expression using current_value...
5 } while(!compare_and_swap(the_variable, current_value, new_value));
  • compare_and_swap()以原子方式检查the_variable的值是否仍为current_value,并且只有这样才会将the_variable的值更新为{{1}并返回new_value

  • 确切的调用语法因CPU而异,可能涉及汇编语言或系统/编译器提供的包装函数(如果可用则使用后者)可能存在其他编译器优化或问题,其使用限制了安全行为);一般来说,检查你的文档

重要的是当另一个线程在第3行读取之后但在第5行的CAS尝试更新之前更新变量时,比较和交换指令将失败,因为您要更新的状态是不是用于计算所需目标状态的那个。这样的do / while循环可以说是“旋转”而不是锁定,因为它们围绕循环循环直到CAS成功。

至关重要的是,您的现有线程库可能需要针对互斥锁,读写锁等采用两阶段锁定方法,包括:

  • 第一阶段:使用CAS或类似物旋转(即旋转{读取当前值,如果未设置则然后cas(current = not set,new = set)}) - 这意味着其他线程快速执行更新通常不会导致您的线程交换等待,以及与之相关的所有相对耗时的开销。

  • 第二阶段仅在超过循环迭代或经过时间的限制时使用:它要求操作系统对线程进行排队,直到它知道(或至少怀疑)锁可以自由获取为止。 / p>

这意味着如果您使用互斥锁来保护对变量的访问,那么您不可能通过实现自己的基于CAS的“互斥锁”来保护同一个变量。

当您直接使用足够小的变量直接使用CAS指令本身进行更新时,

锁定自由算法会自成一体。而不是......

  • 获取一个互斥(通过在CAS上旋转,回到较慢的OS队列上)
  • 更新变量
  • 发布互斥

......只需让CAS上的旋转直接执行变量更新,它们就会被简化(并且变得更快)。当然,你可能会发现从旧的痛苦中计算新价值的工作以推测性地重复,但除非有很多争论,否则你不会经常浪费这些努力。

这种仅更新内存中单个位置的能力具有深远意义,而解决方法可能需要一些创造力。例如,如果您有一个使用无锁算法的容器,您可能决定计算容器中元素的潜在更改,但无法与更新内存中其他位置的大小变量同步。您可能需要没有大小的生活,或者能够使用近似大小的CAS-spin来增加或减小大小,但任何给定的大小读取可能会略有错误。您可能需要合并两个逻辑相关的数据结构(例如空闲列表和元素容器)来共享索引,然后将每个数据结构的核心字段按位打包到每个记录开头的相同原子大小的单词中。这些类型的数据优化可能非常具有侵入性,有时无法获得您想要的行为特征。 Mutexes等在这方面要容易得多,而且至少你知道如果需求只是迈出了那么一步,你就不需要重写互斥体。也就是说,巧妙地使用无锁方法确实可以满足大量需求,并且可以提高性能和可扩展性。

无锁算法的一个核心(好)结果是一个线程无法持有互斥锁然后碰巧被调度程序换出,这样其他线程在恢复之前就无法工作;相反 - 使用CAS - 他们可以安全有效地旋转,而无需操作系统后备选项。

锁定免费算法的东西可以包括更新使用/引用计数器,修改指针以干净地切换指向数据,空闲列表,链表,标记使用/未使用的哈希表桶以及负载平衡。当然还有很多其他人。

正如您所说,只是隐藏在某些API后面使用互斥锁并不是无锁定的。

答案 1 :(得分:2)

有很多不同的同步方法。消息传递有多种变体(例如,CSP)或transactional memory

这两个可以使用锁实现,但这是一个实现细节。

然后,当然,出于某些目的,有无锁算法或数据结构,只需几个原子指令(如比较和交换),但这不是一般的 - 用于更换锁具。

答案 2 :(得分:1)

某些数据结构有多种实现,可以在无锁配置中实现。例如,生产者/消费者模式通常可以使用无锁链表结构来实现。

但是,大多数无锁解决方案需要设计特定程序/特定问题域的人员进行重要考虑。它们通常不适用于所有问题。有关此类实现的示例,请查看Intel's Threading Building Blocks library

最重要的是要注意,没有免锁解决方案是免费的。您将在实现复杂性方面做出最大努力,并且在您在单个核心上运行的情况下可能会提供性能(例如,链接列表比向量慢很多)。确保你在使用无锁定之前进行基准测试,假设它会更快。

附注:我真的希望你没有使用条件变量,因为没有办法确保它们的访问在C和C ++中按照你的意愿运行。

答案 3 :(得分:1)

要添加到阅读列表中的另一个库:Fast Flow

在您的情况下,有趣的是它们基于无锁队列。他们实现了一个简单的无锁队列,然后构建了更复杂的队列。

由于代码是免费的,你可以仔细阅读它并获得无锁队列的代码,这对于正确的做法来说远非微不足道。

相关问题