关于std::mutex::try_lock
可能I'm misunderstanding:
允许此函数虚假失败并返回false,即使互斥锁当前未被任何其他线程锁定。
这意味着如果没有一个线程锁定mutex
,当我尝试try_lock
时,可以返回false
?出于什么目的?
try_lock
如果没有人锁定,false
锁定 OR true
,则SELECT
date,
userId,
SUM(totals.visits) totalvisits,
GROUP_CONCAT(device.deviceCategory) sequentialdevice
FROM (
SELECT
date,
visitStartTime,
customDimensions.value userId,
totals.visits,
device.deviceCategory
FROM
TABLE_DATE_RANGE([164345793.ga_sessions_], TIMESTAMP('20171127'), CURRENT_TIMESTAMP())
WHERE
customDimensions.index = 1
AND customDimensions.value CONTAINS "hip|"
GROUP BY
date,
visitStartTime,
userId,
totals.visits,
device.deviceCategory
HAVING
userId="hip|7e4fbce9-bbfb-4677-aab0-dcd02851fdb4"
ORDER BY
date ASC,
visitStartTime ASC)
GROUP BY
date,
userId
的功能是否会返回SELECT
date,
userId,
totalsvisits,
STRING_AGG(DISTINCT devicecategory ORDER BY date ASC, vstime ASC) deviceAgg
FROM (
SELECT
date,
visitStartTime vstime,
cd.value userId,
totals.visits totalsvisits,
device.deviceCategory devicecategory
FROM
`12314124123123.ga_sessions_*`,
UNNEST(customDimensions) AS cd
WHERE
cd.index=1
AND cd.value IS NOT NULL
GROUP BY
date,
visitStartTime,
userId,
totals.visits,
device.deviceCategory
HAVING
userId="hip|7e4fbce9-bbfb-4677-aab0-dcd02851fdb4"
ORDER BY
date ASC,
visitStartTime ASC)
GROUP BY
date,
userId,
totalsvisits
?不确定我的非母语英语是否在欺骗我......
答案 0 :(得分:5)
这意味着如果没有一个线程锁定该互斥锁,当我尝试try_lock时,它可能返回false?
是的,这正是它所说的。
如果没有锁定,try_lock的函数是否会返回false,如果没有锁定,则返回true?
不,try_lock
的功能是尝试来锁定互斥锁。
然而,失败的方法不止一种:
POSIX-ish平台上的常见情况是从POSIX线程继承的,它是一个信号传递给当前线程(并由信号处理程序处理),从而中断锁定尝试。
在其他平台上可能存在其他特定于平台的原因,但行为是相同的。
答案 1 :(得分:1)
根据你的评论,我会写(引用你的话):
std::unique_lock<std::mutex> lock(m, std::defer_lock); // m being a mutex
...
if (lock.try_lock()) {
... // "DO something if nobody has a lock"
} else {
... // "GO AHEAD"
}
请注意lock.try_lock()
有效地调用m.try_lock()
,因此它也容易出现虚假失败。但我不太关心这个问题。 IMO,在实践中,虚假失败/唤醒是非常罕见的(正如无用的指出,在Linux上,它们可能在信号传递时发生)。
有关虚假问题的更多信息,请参阅:https://en.wikipedia.org/wiki/Spurious_wakeup或Why does pthread_cond_wait have spurious wakeups?。
<强>更新强>
如果你真的想消除try_lock
的虚假失败,你可以使用一些原子旗帜,例如:
// shared by threads:
std::mutex m;
std::atomic<bool> flag{false};
// within threads:
std::unique_lock<std::mutex> lock(m, std::defer_lock); // m being a mutex
...
while (true) {
lock.try_lock();
if (lock.owns_lock()) {
flag = true;
... // "DO something if nobody has a lock"
flag = false;
break;
} else if (flag == true) {
... // "GO AHEAD"
break;
}
}
可能可能会改写为更好的形式,我没有检查。另请注意,flag
不会通过RAII自动取消设置,某些范围保护可能在此处有用。
更新2
如果您还不需要mutex
的屏蔽功能,请使用std::atomic_flag
:
std::atomic_flag lock = ATOMIC_FLAG_INIT;
// within threads:
if (lock.test_and_set()) {
... // "DO something if nobody has a lock"
lock.clear();
} else {
... // "GO AHEAD"
}
再一次,通过一些RAII机制清除标志会更好。
答案 2 :(得分:0)
如果对try_lock()
的调用返回true,则调用成功锁定锁。如果没有,则返回false。这就是全部。是的,当没有其他人拥有锁时,该函数可以返回false。 False意味着仅锁定尝试未成功;它没有告诉你它失败的原因。
答案 3 :(得分:0)
与那里所说的不同,我不认为由于操作系统相关原因导致try_lock
功能失败有任何原因:这种操作是无阻塞的,因此信号无法真正中断它。很可能它与在CPU级别上如何实现此功能有关。毕竟,无争议的案例通常是互斥体最有趣的案例。
互斥锁定通常需要某种形式的原子比较交换
操作。 C ++ 11和C11引入了atomic_compare_exchange_strong
和atomic_compare_exchange_weak
。允许后者虚假失败。
通过允许try_lock
虚假失败,允许实现使用atomic_compare_exchange_weak
来最大限度地提高性能并最小化代码大小。
例如,在ARM64上,原子操作通常使用独占负载(LDXR
)和独占存储(STRX
)指令来实现。 LDXR
启动&#34;监控&#34;硬件开始跟踪对存储区域的所有访问。 STRX
仅在LDXR
和STRX
指令之间未对该区域进行访问时才执行商店。因此,如果另一个线程访问该内存区域或者两者之间存在IRQ,则整个序列可能会失败。
在实践中,使用弱保证实现的try_lock
代码生成与使用强保证实现的代码生成并没有太大差别。
bool mutex_trylock_weak(atomic_int *mtx)
{
int old = 0;
return atomic_compare_exchange_weak(mtx, &old, 1);
}
bool mutex_trylock_strong(atomic_int *mtx)
{
int old = 0;
return atomic_compare_exchange_strong(mtx, &old, 1);
}
看看为ARM64生成的程序集:
mutex_trylock_weak:
sub sp, sp, #16
mov w1, 0
str wzr, [sp, 12]
ldaxr w3, [x0] ; exclusive load (acquire)
cmp w3, w1
bne .L3
mov w2, 1
stlxr w4, w2, [x0] ; exclusive store (release)
cmp w4, 0 ; the only difference is here
.L3:
cset w0, eq
add sp, sp, 16
ret
mutex_trylock_strong:
sub sp, sp, #16
mov w1, 0
mov w2, 1
str wzr, [sp, 12]
.L8:
ldaxr w3, [x0] ; exclusive load (acquire)
cmp w3, w1
bne .L9
stlxr w4, w2, [x0] ; exclusive store (release)
cbnz w4, .L8 ; the only difference is here
.L9:
cset w0, eq
add sp, sp, 16
ret
唯一的区别是&#34;弱&#34; version消除了条件向后分支cbnz w4, .L8
并将其替换为cmp w4, 0
。 CPU预测后向条件分支将被视为&#34;将被采取&#34;因为它们被假定为循环的一部分而忽略了分支预测信息 - 在这种情况下这种假设是错误的,因为大多数时间锁定将被获取(低争用被认为是最常见的情况)。
Imo这是这些功能之间唯一的性能差异。 &#34;斯特朗&#34;在某些工作负载下,单个指令基本上可能存在100%的分支误预测率。
顺便说一句,ARMv8.1引入了原子指令,因此两者之间没有区别,就像在x86_64上一样。使用-march=armv8.1-a
标志生成的代码:
sub sp, sp, #16
mov w1, 0
mov w2, 1
mov w3, w1
str wzr, [sp, 12]
casal w3, w2, [x0]
cmp w3, w1
cset w0, eq
add sp, sp, 16
ret
即使使用try_lock
,某些atomic_compare_exchange_strong
函数也可能失败,例如try_lock_shared
的{{1}}可能需要增加阅读器计数器,如果其他阅读器已输入,则可能会失败锁。 &#34;斯特朗&#34;这种函数的变体需要生成一个循环,因此可能遭受类似的分支错误预测。
另一个小细节:如果mutex是用C语言编写的,那么一些编译器(如Clang)可能会在16字节边界处对齐循环以改善其性能,使用填充来膨胀函数体。如果循环几乎总是运行一次,这是不必要的。
虚假失败的另一个原因是无法获取内部互斥锁(如果使用自旋锁和某些内核原语实现互斥锁)。从理论上讲,同样的原则可以在shared_mutex
的内核实现中获得,尽管这似乎不合理。
答案 4 :(得分:0)
在论文Foundations of the C++ Concurrency Memory Model的第3节中,已经清楚地解释了该标准为何允许try_lock
的虚假故障。简而言之,它是为了使try_lock
的语义与C ++内存模型中race的定义一致。