比如我的Ubuntu系统是双核的。我在上面报告的上下文切换时间的测量使用了 10 个线程链,其中每个线程只是等待其输入信号量被解锁()。当一个线程的输入信号量被解锁时,该线程开始运行……但简短的线程活动只是 1) 增加一个计数并检查一个标志,以及 2) unlock() 下一个线程,以及 3) lock() 它的自己的输入互斥锁,即再次等待上一个任务信号。在该测试中,我们称为 main 的线程使用其中一个线程的 unlock() 启动线程排序,并使用所有线程都可以看到的标志停止它。
在此测量活动期间(大约 3 秒),Linux 系统监视器显示涉及两个内核,并报告两个内核的利用率都在 60% 左右。我预计两个核心都为 100%..不知道为什么不是。
有人可以解释为什么我们在线程函数中使用互斥量吗?感谢
你!
我想 std::mutex 的最常规用途是序列化对内存结构(可能是共享访问存储或结构)的访问。如果您的应用程序具有可由多个线程访问的数据,则每个写访问都必须序列化以防止竞争条件破坏数据。有时,读取和写入访问都需要序列化。 (参见哲学家就餐问题。)
在您的代码中,例如(虽然我不知道您使用的是什么系统), std::cout (共享结构)可能会“交错”文本。也就是说,线程上下文切换可能发生在打印“hello”甚至“hi”的过程中。这种行为通常是不受欢迎的,但可能是可以接受的。
多年前,我与 vxWorks 合作,我的团队学会了在访问 std::cout 时使用互斥锁来消除交错。这种行为可能会分散注意力,而且通常客户不喜欢这种行为。 (最终,对于该应用程序,我们取消了标准三重奏 io(cout、cerr、cin)的使用)
如果允许多个线程“同时”尝试对各种设备进行操作,则各种设备也可能无法正常运行。例如,我为一台设备编写的软件需要 50 us 或更长时间才能完成对我的软件“戳”的反应,然后才能对设备应用任何其他操作。该设备无需等待就直接忽略了我的代码操作。
您还应该知道,有些技术不涉及信号量,而是使用线程和 IPC 来提供序列化(即受保护的)资源访问。
来自维基百科,“在并发编程中,监视器是一种同步构造,它允许线程具有互斥和等待(阻塞)特定条件变为真的能力。”
当 os 提供合适的 IPC 时,我更喜欢使用 Hoare monitor。在我的解释中,监视器只是一个通过 IPC 接受命令的线程,并且是只要线程访问共享结构或设备。当只有 1 个线程访问结构时,不需要互斥体。所有其他线程必须发送消息(通过 IPC)以请求(或可能命令)另一个结构更改。监控线程一次处理一个请求,从 IPC 中按顺序处理。
定义:碰撞
在“线程上下文切换”和“互斥信号量”的上下文中,当线程必须阻塞并等待访问资源时会发生“冲突”,因为该资源已经在“使用中”(即“占用”) . 这是一个强制的上下文切换. 另请参见术语“临界区”。
当共享资源当前未被使用时,没有冲突。 lock() 和 unlock() 几乎没有成本(与上下文切换相比)。
当发生冲突时,上下文切换会使事情变慢“一堆”。但这“一群”可能仍然是可以接受的...考虑与关键部分内的活动持续时间相比,“束”何时较小。
最后说明......有了这个“碰撞”的新想法:
a) 面对许多冲突,多线程的效率可能低得多。
对于意想不到的例子,函数“new”访问一个线程共享资源,我们可以称之为“动态内存”。在一次体验中,每个线程在启动时生成 1000 个新线程。一个线程可以在 0.5 秒内完成这项工作。四个线程,连续快速启动,用了 40 秒来完成 4 次启动。上下文切换!
b) 当你有多个内核并且没有/或很少有冲突时,多线程可以更有效。本质上,如果线程很少交互,它们可以(大部分)同时运行。
当多核和冲突时,线程效率可以是 a 或 b 之间的任何位置。
例如,我的基于 ram 的“日志”机制似乎运行良好——每个日志条目一个互斥锁访问。通常,我有意使用最少的日志记录。在调试“发现的”挑战时,我添加了额外的日志记录(可能稍后删除)以确定出了什么问题。通常,调试器优于一般的日志记录技术。但有时,添加多个日志条目效果很好。