测试和设置是做啥用的?

Posted

技术标签:

【中文标题】测试和设置是做啥用的?【英文标题】:What is Test-and-Set used for?测试和设置是做什么用的? 【发布时间】:2008-09-23 13:22:00 【问题描述】:

在阅读了 Test-and-Set Wikipedia entry 之后,我仍然想知道“Test-and-Set 的用途是什么?”

我知道您可以使用它来实现 Mutex(如***中所述),但它还有什么其他用途?

【问题讨论】:

【参考方案1】:

一个很好的例子是“增量”。

假设有两个线程执行a = a + 1。假设a 以值100 开头。如果两个线程同时运行(多核),则两者都将a 加载为100,递增到101,并将其存储回a。错了!

使用 test-and-set,您说的是“将 a 设置为 101,但前提是它当前具有值 100。”在这种情况下,一个线程将通过该测试,但另一个线程将失败。在失败的情况下,线程可以重试整个语句,这次将a加载为101。成功。

这通常比使用互斥锁更快,因为:

    大多数情况下不存在竞争条件,因此无需获取某种互斥锁即可进行更新。 即使在冲突期间,一个线程也不会被阻塞,而另一个线程只是旋转并重试比将自身挂起以等待某个互斥体更快。

【讨论】:

@jason-cohen:这实际上是对Compare and Swap 的描述。测试和设置通常只涉及值 0 和 1。set 部分是指将指定内存位置的值设置为 1。它返回之前的值,1 或 0,并执行所有这些都在一个原子操作中。 @GregSlepak 是正确的,这是 compare_and_swap。 test_and_set() 接受一个指向目标的布尔指针,将其设置为 TRUE 并返回指针的原始值。如果test_and_set(&lock)的返回值(即&lock的原始值)为真,则进入临界区。 同意@GregSlepak,上面的回复是比较和交换的一个例子,而不是测试和设置。【参考方案2】:

您在完成一些工作后想要将数据写入内存的任何时候都可以使用它,并确保自您开始以来另一个线程没有覆盖目标。很多lock/mutex-free algorithms 都采用这种形式。

【讨论】:

【参考方案3】:

假设您正在编写一个银行应用程序,并且您的应用程序请求从帐户中提取 10 英镑(是的,我是英国人;))。所以需要将当前账户余额读入一个局部变量,减去取款,然后将余额写回内存。

但是,如果在读取值和写出值之间发生另一个并发请求怎么办?该请求的结果可能会被第一个完全覆盖,并且帐户余额将不正确。

Test-and-set 通过检查覆盖的值是否是您认为的值来帮助我们解决该问题。在这种情况下,您可以检查余额是否是您读取的原始值。由于它是原子的,因此它是不可中断的,因此没有人可以在读取和写入之间从您身下拉出地毯。

解决相同问题的另一种方法是对内存位置进行锁定。不幸的是,锁非常难以正确,难以推理,存在可伸缩性问题并且在面对故障时表现不佳,因此它们不是理想(但绝对实用)的解决方案。测试和设置方法构成了一些软件事务内存的基础,它乐观地允许每个事务同时执行,但如果它们发生冲突则以回滚它们为代价。

【讨论】:

【参考方案4】:

考虑到原子性的巨大重要性,基本上,它的用途正是用于互斥锁。就是这样。

Test-and-set 是一种可以使用另外两条指令执行的操作,非原子指令和更快(在多处理器系统上原子性承担硬件开销),因此通常您不会出于其他原因使用它。

【讨论】:

【参考方案5】:

当您需要获取共享值、对其执行某些操作并更改该值时使用它,假设另一个线程尚未更改它。

至于实际用途,我最后一次看到它是在并发队列的实现中(可以由多个线程推送/弹出而不需要信号量或互斥体的队列)。

为什么要使用 TestAndSet 而不是互斥锁?因为它通常比互斥锁需要更少的开销。在互斥体需要操作系统干预的情况下,可以将 TestAndSet 实现为 CPU 上的单个原子指令。在具有 100 多个线程的并行环境中运行时,关键代码部分中的单个互斥锁可能会导致严重的瓶颈。

【讨论】:

mutex 内部可能使用测试和设置。你如何比较 TestAndSet 和互斥锁。我觉得比较不合适 在 Python 中,互斥对象明确地有一个“testandset”方法。文档将其称为“原子”,带有引号,这并不能激发信心,但它确实表明这两个概念不是,呃,互斥的。【参考方案6】:

也可以用来实现自旋锁:

void spin_lock(struct spinlock *lock)

        while (test_and_set(&lock->locked));

【讨论】:

【参考方案7】:

Test-and-set Lock (TLS) 用于实现进入临界区。

TLS <destination> <origin>

一般来说,TLS 是由两个步骤组成的原子操作:

    将值从origin复制到destinationorigin 的值设置为1

让我们看看如何使用 TLS CPU 指令实现一个简单的互斥锁进入临界区。

我们需要一个将用作共享资源的内存单元。我们称之为lock。对我们来说重要的是,我们可以为这个内存单元设置 0 或 1 值。

然后进入临界区会是这样的:

enter_critical_section:
    TLS <tmp>, <lock>           ; copy value from <lock> to <tmp> and set <lock> to 1
    CMP <tmp>, #0               ; check if previous <lock> value was 0
    JNE enter_critical_section  ; if previous <lock> value was 1, it means that we didn't enter the critical section, and must try again
    RET                         ; if previous <lock> value was 0, we entered critical section, and can return to the caller

要离开临界区,只需将 lock 的值设置回 0:

leave_critical_section:
  MOV <lock>, #0  
  RET

附言

例如,在 x86 中有一个XCHG instruction,它允许与另一个寄存器交换注册表/内存的值。

XCHG <destination> <origin>

XCHG指令进入临界区的实现:

enter_critical_section:
    MOV <tmp>, #1
    XCHG <tmp>, <lock>
    CMP <tmp>, #0
    JNE enter_critical_section
    RET

【讨论】:

以上是关于测试和设置是做啥用的?的主要内容,如果未能解决你的问题,请参考以下文章

SetPixelFormat() 中的 PIXELFORMATDESCRIPTOR 参数是做啥用的?

cursor.setNotificationUri() 是做啥用的?

EQ是做啥用的,它和压缩一样吗.如果不一样那压缩是做啥用的~

sigaddset 是做啥用的?

渠道是做啥用的?

Spring Security:UserDetailsManager 接口是做啥用的?和更多!