【问题标题】:linux thread synchronizationlinux线程同步
【发布时间】:2010-04-07 18:25:08
【问题描述】:

我是 linux 和 linux 线程的新手。我花了一些时间在谷歌上搜索,试图了解所有可用于线程同步的函数之间的差异。我还有一些问题。

我发现了所有这些不同类型的同步,每个都有许多用于锁定、解锁、测试锁等的功能。

  • gcc 原子操作
  • futex
  • 互斥体
  • 自旋锁
  • seqlocks
  • rculocks
  • 条件
  • 信号量

我目前(但可能有缺陷)的理解是这样的:

信号量是进程范围的,涉及文件系统(实际上我假设),并且可能是最慢的。

Futex 可能是互斥锁、自旋锁、seqlock 和 rculock 使用的基本锁定机制。 Futex 可能比基于它们的锁定机制更快。

自旋锁不会阻塞,因此可以避免上下文切换。然而,它们以消耗 CPU 上的所有周期为代价来避免上下文切换,直到锁被释放(旋转)。出于显而易见的原因,它们应该只应用于多处理器系统。永远不要睡在自旋锁里。

如果作者更改了工作所基于的数据,seq 锁只会告诉您何时完成工作。在这种情况下,您必须返回并重复该工作。

原子操作是最快的同步调用,并且可能用于上述所有锁定机制。您不想对共享数据中的所有字段使用原子操作。当您访问多个数据字段时,您希望在锁定标志上使用锁(互斥锁、futex、自旋、序列、rcu)或单个原子操作。

我的问题是这样的:

  1. 到目前为止,我的假设是否正确?

  2. 有人知道各种选项的 CPU 周期成本吗? 我正在为应用程序添加并行性,这样我们就可以在每个盒子运行更少的应用程序实例的情况下获得更好的墙上时间响应。性能是最重要的考虑因素。我不想通过上下文切换、旋转或大量额外的 cpu 周期来读取和写入共享内存来消耗 cpu。我非常关心消耗的 CPU 周期数。

  3. 哪些锁(如果有)可以防止调度程序或中断中断线程......或者我只是一个白痴,所有同步机制都这样做。防止了哪些类型的中断?我可以阻止所有线程或仅在锁定线程的 CPU 上的线程吗? 这个问题源于我害怕中断持有一个非常常用函数的锁的线程。我希望调度程序可能会调度任何数量的其他工作人员,这些工作人员可能会遇到这个函数,然后因为它被锁定而阻塞。大量的上下文切换将被浪费,直到带有锁的线程被重新调度并完成。我可以重新编写这个函数来最小化锁定时间,但它仍然很常见,我想使用一个防止中断的锁......跨所有处理器。

  4. 我正在编写用户代码...所以我得到的是软件中断,而不是硬件中断...对吗?我应该远离任何包含“irq”一词的函数(自旋/序列锁)。

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

  6. 有没有人认为使用原子操作让多个线程在链表中移动是疯了? 我正在考虑以原子方式将当前项指针更改为列表中的下一项。如果尝试成功,则线程可以安全地使用当前项目在移动之前指向的数据。其他线程现在将沿列表移动。

  7. futex?有什么理由使用它们而不是互斥锁?

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

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

我希望使用互斥锁或自旋锁来少量保护我希望系统上的一个线程(而不是 CPU)一次访问的代码部分。我希望谨慎地使用 gcc 原子操作来选择工作并尽量减少互斥锁和自旋锁的使用。例如:可以检查工作项中的标志以查看线程是否已工作(0=否,1=是或正在进行中)。一个简单的 test_and_set 告诉线程它是否有工作或需要继续。我希望在有工作的时候使用条件来唤醒线程。

谢谢!

【问题讨论】:

  • 你是在做应用程序编程还是内核编程?
  • 应用程序编程。
  • 感谢所有回答的人。我们求助于使用 gcc 原子操作来同步我们所有的线程。原子操作比在不同步的情况下设置值慢大约 2 倍,但比锁定互斥体、更改值然后解锁互斥体要快得多(当您开始让线程进入锁时,这变得超级慢......)我们仅使用 pthread_create、attr、cancel 和 kill。我们使用 pthread_kill 来通知线程唤醒我们进入睡眠状态。这种方法比 cond_wait 快 40 倍。所以基本上......如果你有时间可以使用 pthreads_mutexes。
  • 你能举个例子说明你是如何摆脱 cond_wait 的吗??
  • 我会看看我是否可以挖掘一个例子。这是一般的想法。入队使用原子操作来构建工作项树,线程使用原子操作从树中获取工作。如果正在等待的子树上有未开始的工作,则等待线程完成工作。如果等待线程无能为力(没有未开始的工作,但有工作正在进行),我们使用 sigwait() 使线程休眠,并使用 CAS 将线程链接到工作项上的睡眠者列表。当工作完成时,它会发送 pthread_kill 给所有正在休眠的线程以唤醒它们。

标签: linux performance synchronization multithreading locking


【解决方案1】:

应用程序代码可能应该使用 posix 线程函数。我假设你有手册页所以输入

man pthread_mutex_init
man pthread_rwlock_init
man pthread_spin_init

阅读它们以及对它们进行操作的函数,以确定您需要什么。

如果您正在进行内核模式编程,那就另当别论了。您需要了解自己在做什么、需要多长时间以及调用它的上下文,以了解您需要使用什么。

【讨论】:

  • 谢谢。不知何故,我错过了 posix 线程函数中的 rwlock 和 spinlock 。当 pthread 持有上述锁之一时,它的可中断性有什么想法吗?
  • 可中断性只是内核代码的问题。对于应用程序代码,您不能禁用中断。
【解决方案2】:

感谢所有回答的人。我们求助于使用 gcc 原子操作来同步我们所有的线程。原子操作比在没有同步的情况下设置值慢大约 2 倍,但比锁定互斥体、更改值然后解锁互斥体要快得多(当您开始让线程进入锁时,这变得非常慢......)我们仅使用 pthread_create、attr、cancel 和 kill。我们使用 pthread_kill 来通知线程唤醒我们进入睡眠状态。这种方法比 cond_wait 快 40 倍。所以基本上......如果你有时间浪费,请使用 pthreads_mutexes。

【讨论】:

    【解决方案3】:

    另外你应该检查下一本书籍

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

    和

    • 使用 POSIX(R) 线程编程

    【讨论】:

      【解决方案4】:

      关于问题 #8 有没有比在没有工作时使用条件休眠线程更好的方法? 是的,我认为最好的方法不是使用睡眠 正在使用“semaphore.h”的 sem_post() 和 sem_wait 等函数

      问候

      【讨论】:

      • 更多同步功能!哈哈。不过,我害怕信号量性能……这是一种无效的恐惧吗?
      • 是的,你应该知道在 Linux 内核中没有什么叫做“互斥”的东西。有一个宏,将信号量初始化为互斥体
      • Linux 内核使用互斥锁已有大约 5 年了。 git.kernel.org/?p=linux/kernel/git/torvalds/…
      【解决方案5】:

      关于 futexes 的说明 - 它们被更具描述性地称为 快速用户空间互斥体。使用 futex,仅在需要仲裁时才涉及内核,这提供了加速和节省。

      实现 futex 可以是 extremely tricky (PDF),调试它们可能会导致疯狂。除非您真的非常非常需要速度,否则通常最好使用 pthread 互斥锁实现。

      同步从来都不是easy,但是尝试在用户空间中实现自己的同步会非常困难。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-03-13
        • 2019-09-28
        • 2020-07-29
        • 2014-04-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多