Linux线程同步

5
我对Linux和Linux线程很陌生。我花了一些时间在Google上搜索,试图理解所有可用于线程同步的函数之间的差异。但我仍有一些疑问。
我发现了下列不同类型的同步机制,并且每种机制都有锁定、解锁、测试锁等多个功能。
- GCC原子操作 - Futexes - 互斥锁 - 自旋锁 - 序列锁 - RCU锁 - 条件变量 - 信号量
我的当前(虽然可能存在缺陷)理解如下:
- 信号量是跨进程的,在文件系统中涉及(我认为是虚拟的),速度可能最慢。 - Futexes 可能是互斥锁、自旋锁、序列锁和RCU锁所使用的基本锁定机制。它们可能比基于它们的锁定机制更快。 - 自旋锁不阻塞,因此避免了上下文切换。但它们以耗尽CPU周期(旋转)来避免上下文切换。它们应仅在多处理器系统上使用,原因是显而易见的。永远不要在自旋锁中休眠。 - 序列锁告诉您什么时候完成工作,如果写入器更改了基于其数据的工作,则必须返回并重复该工作。 - 原子操作是最快的同步调用,并且可能在上述所有锁定机制中使用。您不想在共享数据中使用所有字段的原子操作。当您访问多个数据字段时,应使用锁(互斥锁、Futex、自旋锁、序列锁、RCU)或单个原子操作来对锁标志进行操作。
我的问题如下:
  1. 我的假设是否正确?

  2. 有人知道各种选项的cpu周期成本吗?我正在为应用程序添加并行性,以便在运行更少的应用程序实例的代价下获得更好的响应时间。性能是最重要的考虑因素。我不想通过上下文切换、旋转或大量额外的cpu周期来读写共享内存来消耗cpu。我绝对关心消耗的cpu周期数。

  3. 锁中哪些可以防止调度程序或中断线程...还是我只是个白痴,所有同步机制都这样做。防止哪些种类的中断?我可以阻止所有线程或仅阻止锁定线程的CPU上的线程?这个问题源于我担心打断持有锁的线程,该线程持有非常常用的函数。我预计调度程序可能会安排任意数量的其他工作线程,这些线程可能会遇到此函数,然后阻塞,因为它被锁定了。大量的上下文切换将浪费,直到具有锁的线程重新安排并完成。我可以重新编写此函数以最小化锁定时间,但仍然如此常被调用,我希望使用一个可以防止中断的锁...跨所有处理器。

  4. 我正在编写用户代码...所以我得到的是软件中断,而不是硬件中断...对吗?我应该避开任何带有“irq”单词的函数(旋转/序列锁)。

  5. 哪些锁是用于编写内核或驱动程序代码的,哪些是用于用户模式的?

  6. 有人认为使用原子操作让多个线程通过链接列表是疯狂的吗?我考虑原子地将当前项目指针更改为列表中的下一个项目。如果尝试成功,则线程可以安全地使用当前项目指向的数据,然后将其他线程移动到列表上。

  7. futexes?有没有使用它们而不是互斥锁的理由?

  8. 是否有比使用条件来使线程在没有工作时睡眠更好的方法?

  9. 在使用gcc原子操作时,特别是test_and_set时,是否可以通过先进行非原子测试,然后使用test_and_set进行确认来获得性能提升?我知道这将是特定情况,因此这里是情况。有大量的工作项,例如数千个。每个工作项都有一个初始化为0的标志。当线程具有独占访问工作项时,该标志将为1。将有大量的工作线程。任何时候线程正在寻找工作时,它们可以进行非原子测试以获取1。如果他们读取了一个1,我们就能确定工作不可用。如果他们读取0,则需要执行原子test_and_set进行确认。因此,如果原子test_and_set是500个cpu周期,因为它禁用流水线,导致cpu通信和L2缓存刷新/填充....而简单测试仅需1个周期....那么只要在遇到已经完成的工作项方面,我有更好的500:1比率....这将是一个胜利。

我希望使用互斥锁或自旋锁来保护代码中我想让系统中的一个线程(而不仅仅是CPU)在同一时间访问的部分。我希望节约使用gcc原子操作来选择工作并最小化使用互斥锁和自旋锁。例如:可以检查工作项中的标志以查看线程是否已处理它(0=否,1=是或正在进行中)。简单的test_and_set告诉线程它是否有工作要做或需要继续执行。我希望使用条件来唤醒线程,当有工作时。谢谢!

1
你是想进行应用程序编程还是内核编程? - nategoose
应用程序编程。 - johnnycrash
你能否给我一个例子,说明你如何成功摆脱了 cond_wait ? - Raffo
我看看能不能找到一个例子。这是一般的想法。入队使用原子操作来构建工作项树,线程使用原子操作从树中获取工作。如果在等待的子树中有未启动的工作,则等待的线程执行该工作。如果等待的线程无法执行任何操作(没有未启动的工作但有正在进行的工作),我们使用CAS将线程链接到工作项上的睡眠列表,并使用sigwait()使线程进入睡眠状态。当工作完成时,它会向所有睡眠线程发送pthread_kill以唤醒它们。 - johnnycrash
投票关闭,因为它包含太多问题。请参见:https://dev59.com/7L0bzogBFxS5KdRjrhVh - Ciro Santilli OurBigBook.com
显示剩余3条评论
5个回答

2

应用程序代码应该使用posix线程函数。我假设您有man页面,因此输入以下命令:

man pthread_mutex_init
man pthread_rwlock_init
man pthread_spin_init

阅读相关文档并了解函数的使用方法,以确定您需要哪些内容。

如果您正在进行内核模式编程,则情况就不同了。您需要对自己所做的事情有所了解,知道它需要多长时间,并且在什么上下文中被调用,才能对您需要使用的内容有任何想法。


谢谢。我在posix线程函数中错过了rwlock和spinlock。当pthread持有上述锁之一时,您有关于其可中断性的任何想法吗? - johnnycrash
中断仅对内核代码是一个问题。对于应用程序代码,您无法禁用中断。 - nategoose

2
感谢所有回答的人。我们采用了gcc原子操作来同步所有线程。原子操作比未同步设置值慢约2倍,但比锁定互斥体、更改值,然后解锁互斥体(当您开始使用线程碰撞锁时会变得非常缓慢...)快得多。我们只使用pthread_create、attr、cancel和kill。我们使用pthread_kill来向我们放置在睡眠中的线程发出唤醒信号。这种方法比cond_wait快40倍。因此,基本上......如果您有时间浪费,请使用pthread_mutexes。

原子操作比没有同步的设置值慢了约2倍。如果您想要原子性而不需要同步,请使用std::atomic<T>std::memory_order_relaxed。或者,如果您想自己编写代码,则可以使用GNU C __atomic_store_n__ATOMIC_RELAXED。这将编译为普通的存储指令。https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html(`__sync`内置函数已被`__atomic`淘汰)。如果您谈论的是原子RMW,而不仅仅是load/store,那么实现正确性(原子性!)就必须更慢。 - Peter Cordes

0
关于第8个问题 在没有工作时,有没有比使用条件来使线程休眠更好的方法? 是的,我认为最好的方法不是使用sleep, 而是使用类似于“semaphore.h”的sem_post()和sem_wait()函数。
敬礼

更多的同步函数!哈哈。虽然我有点担心信号量的性能问题...这种担心是无效的吗? - johnnycrash
是的,确实应该记住,在Linux内核中没有叫做“mutex”的东西。有一个宏,它将信号量初始化为互斥体。 - Sebastian Marcet
Linux内核已经使用互斥锁约5年了。http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=blob;f=include/linux/mutex.h;h=878cab4f5fcc5db95585184c22aea5d905a34295;hb=HEAD - Dipstick

0

此外,您还应该查看以下书籍:

  • Pthreads编程:更好的多处理的POSIX标准

以及

  • 使用POSIX(R)线程编程

0
关于Futex的说明 - 它们更具描述性地称为快速用户空间互斥锁。使用Futex时,仅在需要仲裁时才涉及内核,这就提供了加速和节省资源的功能。
实现Futex可能会非常棘手(PDF),调试它们可能会让人发疯。除非你真的、真的、真的需要速度,否则最好使用pthread互斥锁实现。
同步从来都不是易事,但是在用户空间中尝试实现它使它异常困难。

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