ダブルロックの有用性について
2016.08.01 サイボウズラボユースにて. ずいぶんまえに学んだ内容だが、資料を生理していたらmemoがでてきたので。 ここに吐いておく(間違っていたらすみません)
はじめに通常のロックについて
以下の手順で行われる
lock() if (inited == false) init() inited = true unlock()
このときif文とlock,unlockのレイテンシの重さはだいたい以下のような関係
if() <<<< lock(), unlock()
初期化をする人は以下のように実行する
lock() if (inited == false) init() inited = true unlock()
それ以降の人はこのように実行する
lock() unlock()
このときの初期化をする人、それ以降の人の割合は以下のような関係
初期化者 <<<<< 超えられない壁 <<<<<< それ以外
なので、いちいちlock, unlockを毎回させるのは嫌だ!!
これがダブルロックのモチベーション
ダブルロックとは
以下のような手順で行われる
if (inited == false) lock() if (inited == false) init() inited = true unlock()
初期化者は以下のように実行
if (inited == false) lock() if (inited == false) init() inited = true unlock()
それ以外の人は以下のように実行
if (inited == false)
見てわかるようにlock,unlockを回避できた。だがしかし、2000年くらいに ただのダブルロックがすべてのアーキテクチャで安全ではないことが示された。
Intelのアーキとかだと、大体うまく動くようだが、Javaとかだと様々なアーキで 動かすことになるので、その問題が浮き彫りになったっぽい。 その出来事以降、Intelのプロセッサのマニュアルにもその趣旨に関する文が追加されている。 詳しくは以下の4/21日を参照する。
http://homepage1.nifty.com/herumi/diary/1204.html
C++11以降はスレッドセーフなロック手法を使っている。
C++のスレッドセーフロックの確認
以下のコードをコンパイルしてasmを見る
struct A { int a; A(int b) : a(b) {} }; A& get_instance() { static A a(2); return a; }
$ c++ -std=c++11 -S -Ofast -o out.s main.cc
.section __TEXT,__text,regular,pure_instructions .macosx_version_min 10, 11 .globl __Z12get_instancev .align 4, 0x90 __Z12get_instancev: ## @_Z12get_instancev .cfi_startproc ## BB#0: pushq %rbp Ltmp0: .cfi_def_cfa_offset 16 Ltmp1: .cfi_offset %rbp, -16 movq %rsp, %rbp Ltmp2: .cfi_def_cfa_register %rbp movb __ZGVZ12get_instancevE1a(%rip), %al testb %al, %al jne LBB0_3 ## BB#1: leaq __ZGVZ12get_instancevE1a(%rip), %rdi callq ___cxa_guard_acquire testl %eax, %eax je LBB0_3 ## BB#2: movl $2, __ZZ12get_instancevE1a(%rip) leaq __ZGVZ12get_instancevE1a(%rip), %rdi callq ___cxa_guard_release LBB0_3: leaq __ZZ12get_instancevE1a(%rip), %rax popq %rbp retq .cfi_endproc .zerofill __DATA,__bss,__ZZ12get_instancevE1a,4,2 ## @_ZZ12get_instancevE1a .zerofill __DATA,__bss,__ZGVZ12get_instancevE1a,8,3 ## @_ZGVZ12get_instancevE1a .subsections_via_symbols
これがスレッドセーフなロックのasm. __cxa_guard_acquire
, __cxa_guard_release
がそれである
こうするとスレッドセーフでない従来型なasmをはいてくれる
$ c++ -std=c++11 -S -Ofast -o out.s main.cc -fno-threadsafe-statics
.section __TEXT,__text,regular,pure_instructions .macosx_version_min 10, 11 .globl __Z12get_instancev .align 4, 0x90 __Z12get_instancev: ## @_Z12get_instancev .cfi_startproc ## BB#0: pushq %rbp Ltmp0: .cfi_def_cfa_offset 16 Ltmp1: .cfi_offset %rbp, -16 movq %rsp, %rbp Ltmp2: .cfi_def_cfa_register %rbp movb __ZGVZ12get_instancevE1a(%rip), %al testb %al, %al jne LBB0_2 ## BB#1: movl $2, __ZZ12get_instancevE1a(%rip) movb $1, __ZGVZ12get_instancevE1a(%rip) LBB0_2: leaq __ZZ12get_instancevE1a(%rip), %rax popq %rbp retq .cfi_endproc .zerofill __DATA,__bss,__ZZ12get_instancevE1a,4,2 ## @_ZZ12get_instancevE1a .zerofill __DATA,__bss,__ZGVZ12get_instancevE1a,1,0 ## @_ZGVZ12get_instancevE1a .subsections_via_symbols
これが従来型。 以上でした。