阻塞的互斥锁有多昂贵?

时间:2018-08-08 11:14:16

标签: c++ multithreading locking mutex

说我有一个互斥锁,线程1锁定了该互斥锁。现在,线程2尝试获取该锁,但是它被阻塞了,例如持续了几秒钟。这个被阻塞的线程多少钱?是否可以将执行中的硬件线程重新安排为执行计算上更昂贵的事情?如果是,那么谁来检查互斥锁是否被解锁?

编辑:好的,所以我尝试重新表述我想问的问题。

我真正不了解的是以下内容的工作原理。线程2被阻塞,那么线程2到底做什么?从答案看来,似乎不只是不断检查互斥锁是否已解锁。如果是这种情况,我会认为阻塞线程很昂贵,因为我使用自己的硬件线程中的一个只是为了检查布尔值是否发生了变化。

所以我是否正确地认为,当互斥锁被线程1释放时,线程1会通知sheduler,而表皮分配了一个硬件线程来执行正在等待的线程2?

2 个答案:

答案 0 :(得分:2)

我将您的问题读为:

  

锁定的互斥锁有多昂贵?

Mutex可被视为内存中的整数。 试图锁定互斥锁的线程必须读取互斥锁的现有状态,并可以根据读取的值进行设置。

test_and_set( &mutex_value, 0, 1 );   // if mutex_value is 0, set to 1

诀窍是读写操作(也称为测试设置)都应该是原子的。原子性是通过CPU支持实现的。

但是,测试设置操作没有提供任何阻止/等待机制。 CPU不了解互斥锁上的线程阻塞情况。操作系统负责通过向用户提供系统调用来管理阻塞。实施因操作系统而异。如果是Linux,则可以考虑使用futex或pthreads。

使用互斥锁的总成本总计为测试设置操作和用于实现互斥锁的系统调用的总和。 测试和设置操作几乎是恒定的,与其他操作所能达到的成本相比,微不足道。

如果有多个线程试图获取锁,则花费     互斥锁可以被认可为以下内容:

    1. Kernel scheduling overhead cost
    2. Context switch overhead cost

内核调度开销

如果一个线程已经获得了互斥锁,那么其他线程会怎样?

其他线程将继续。如果任何其他线程试图锁定已锁定的互斥锁,则OS将重新安排其他线程以等待。原始线程解锁互斥锁后,内核将立即唤醒等待互斥锁的线程之一。

上下文切换开销

用户空间代码的设计方式应使线程花费很少的时间来尝试锁定互斥锁。如果您有多个线程试图在多个位置获取互斥锁,则可能会导致灾难,并且性能可能会差于单个线程为所有请求提供服务。

  

可以重新分配执行中的硬件线程来执行某些操作   计算上更昂贵?

如果我正确地提出了您的问题,则可以根据调度机制对已获得锁的线程进行上下文切换。但是,这本身就是多线程编程的开销。 您可以提供一个用例,以明确定义此问题吗?

  

谁检查互斥锁是否被解锁?

绝对是OS调度程序。请注意,这不仅仅是盲目的sleep()。

答案 1 :(得分:0)

线程只是逻辑上的操作系统概念。没有“硬件线程”。硬件具有核心。操作系统计划内核在一定时间内运行线程。如果某个线程被阻塞,则总是有很多可以运行。

以您的示例为例,如果使用互斥锁,则如果线程2被阻止,则OS会将其取消调度,并将其放入与该互斥锁关联的队列中。当线程1释放锁时,它将通知调度程序,该调度程序将线程2从队列中移出并将其放回调度程序。被阻塞的线程未使用计算资源。但是,实际的锁定/解锁操作会涉及开销,这是操作系统调度调用。

开销并不是微不足道的,因此,如果您有更长的任务(合理地长于调度时间片)并且锁竞争不太多,通常会使用互斥锁。

  

因此,如果锁超出范围,则析构函数中会有一些代码告诉OS锁定的互斥锁现在已解锁?   引用

如果std::mutex在锁定时超出范围,则这是未定义的行为。 (https://en.cppreference.com/w/cpp/thread/mutex/~mutex)即使使用非标准互斥量实现,也可以期望在超出范围之前将其解锁。

请记住,还有其他类型的“锁”(例如spinlock ...它本身有很多版本),但是我们在这里只讨论互斥锁。