[英]What does the “lock” instruction mean in x86 assembly?
我在 Qt 的源代碼中看到了一些 x86 程序集:
q_atomic_increment:
movl 4(%esp), %ecx
lock
incl (%ecx)
mov $0,%eax
setne %al
ret
.align 4,0x90
.type q_atomic_increment,@function
.size q_atomic_increment,.-q_atomic_increment
從谷歌搜索,我知道lock
指令會導致 CPU 鎖定總線,但我不知道 CPU 何時釋放總線?
關於上面的整個代碼,我不明白這段代碼是如何實現Add
?
LOCK
本身不是一條指令:它是一個指令前綴,適用於后面的指令。 該指令必須的東西做一個讀-修改-寫內存( INC
, XCHG
, CMPXCHG
等)---在這種情況下,它是incl (%ecx)
其中指令inc
rements的l
舉行的地址翁詞在ecx
寄存器中。
LOCK
前綴確保 CPU 在操作期間對適當的緩存行擁有獨占所有權,並提供某些額外的排序保證。 這可以通過斷言總線鎖定來實現,但 CPU 將在可能的情況下避免這種情況。 如果總線被鎖定,那么它只是在鎖定指令的持續時間內。
此代碼將ecx
堆棧遞增的變量的地址復制到ecx
寄存器中,然后lock incl (%ecx)
以將該變量自動遞增 1。接下來的兩條指令設置eax
寄存器(保存返回值)如果變量的新值為 0,則從函數)到 0,否則為 1。 該操作是increment ,而不是 add (因此得名)。
您可能無法理解的是,增加值所需的微代碼要求我們首先讀取舊值。
Lock 關鍵字強制實際發生的多條微指令以原子方式操作。
如果您有 2 個線程,每個線程都試圖增加相同的變量,並且它們同時讀取相同的原始值,那么它們都增加到相同的值,並且它們都寫出相同的值。
不是讓變量增加兩次,這是典型的期望,你最終增加了一次變量。
lock 關鍵字可防止這種情況發生。
從谷歌,我知道鎖定指令會導致 cpu 鎖定總線,但我不知道 cpu 何時釋放總線?
LOCK
是一個指令前綴,因此它只適用於下面的指令,來源在這里沒有說得很清楚,但真正的指令是LOCK INC
。 所以總線被鎖定為增量,然后解鎖
關於上面的整個代碼,我不明白這些代碼是如何實現Add的?
它們不實現 Add ,它們實現了一個增量,如果舊值為 0 則返回指示。添加將使用LOCK XADD
(但是,windows InterlockedIncrement/Decrement 也使用LOCK XADD
實現)。
最小可運行 C++ 線程 + LOCK 內聯匯編示例
主程序
#include <atomic>
#include <cassert>
#include <iostream>
#include <thread>
#include <vector>
std::atomic_ulong my_atomic_ulong(0);
unsigned long my_non_atomic_ulong = 0;
unsigned long my_arch_atomic_ulong = 0;
unsigned long my_arch_non_atomic_ulong = 0;
size_t niters;
void threadMain() {
for (size_t i = 0; i < niters; ++i) {
my_atomic_ulong++;
my_non_atomic_ulong++;
__asm__ __volatile__ (
"incq %0;"
: "+m" (my_arch_non_atomic_ulong)
:
:
);
__asm__ __volatile__ (
"lock;"
"incq %0;"
: "+m" (my_arch_atomic_ulong)
:
:
);
}
}
int main(int argc, char **argv) {
size_t nthreads;
if (argc > 1) {
nthreads = std::stoull(argv[1], NULL, 0);
} else {
nthreads = 2;
}
if (argc > 2) {
niters = std::stoull(argv[2], NULL, 0);
} else {
niters = 10000;
}
std::vector<std::thread> threads(nthreads);
for (size_t i = 0; i < nthreads; ++i)
threads[i] = std::thread(threadMain);
for (size_t i = 0; i < nthreads; ++i)
threads[i].join();
assert(my_atomic_ulong.load() == nthreads * niters);
assert(my_atomic_ulong == my_atomic_ulong.load());
std::cout << "my_non_atomic_ulong " << my_non_atomic_ulong << std::endl;
assert(my_arch_atomic_ulong == nthreads * niters);
std::cout << "my_arch_non_atomic_ulong " << my_arch_non_atomic_ulong << std::endl;
}
編譯並運行:
g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp -pthread
./main.out 2 10000
可能的輸出:
my_non_atomic_ulong 15264
my_arch_non_atomic_ulong 15267
從這里我們看到 LOCK 前綴使添加原子化:沒有它我們在許多添加上有競爭條件,並且最后的總數小於同步的 20000。
LOCK 前綴用於實現:
std::atomic
: std::atomic
到底是什么?atomic_int
: 如何在普通 C 語言中啟動線程?另請參閱: 多核匯編語言是什么樣的?
在 Ubuntu 19.04 amd64 中測試。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.