同時実行環境では、単純なことは奇妙に見えることがあります。これが役立つことを願っています。
我々は持っているBUILT-IN ATOMIC CAS1はこのセマンティックを持ちます:
int CAS1(int *addr, int oldval, int newval) {
int currval = *addr;
if (currval == oldval) *addr = newval;
return currval;
}
CAS1を使用し、次の意味を持つATOMIC RDCSS関数を定義する必要があります。
int RDCSS(int *addr1, int oldval1, int *addr2, int oldval2, int newval2) {
int res = *addr;
if (res == oldval2 && *addr1 == oldval1) *addr2 = newval2;
return res;
}
直観的に:* addr1 == oldval1 ...の場合のみ、addr2の値を同時に変更する必要があります。別のスレッドが変更している場合、他のスレッドが操作を完了できるようにしてから、再試行できます。
RDCSS関数を使用して(記事を参照)、CASNを定義します。ここで、次の方法でRDCSS記述子を定義します。
RDCSSDESCRI
int *addr1
int oldval1
int *addr2
int oldval2
int newval2
次に、RDCSSを次のように実装します。
int RDCSS( RDCSSDESCRI *d ) {
do {
res = CAS1(d->addr2, d->oldval2, d); // STEP1
if (IsDescriptor(res)) Complete(res); // STEP2
} while (IsDescriptor(res); // STEP3
if (res == d->oldval2) Complete(d); // STEP4
return res;
}
void Complete( RDCSSDESCRI *d ) {
int val = *(d->addr1);
if (val == d->oldval1) CAS1(d->addr2, d, d->newval2);
else CAS1(d->addr2, d, d->oldval2);
}
- ステップ1:最初に* addr2の値を(独自の)記述子dに変更しようとします。CAS1が成功した場合、res == d-> oldval2(つまり、resは記述子ではありません)
- STEP2:resが記述子かどうかを確認します。つまり、STEP1が失敗しました(別のスレッドがaddr2を変更しました)...別のスレッドが操作を完了するのを助けます
- STEP3:記述子の保存に失敗した場合は、STEP1を再試行しますd
- STEP4:addr2から期待値をフェッチした場合、記述子(ポインター)をaddr2に格納することに成功し、newval2を* addr2 iif * addr1 == oldval1に格納するタスクを完了することができます。
あなたの質問への回答
STEP4を省略した場合、RDCSSセマンティックスのif(... && * addr1 == oldval1)* addr2 = newval2部分は実行されません(...またはそれ以上:他のスレッドを支援することによって予測できない方法で実行される可能性があります)現在のもの)。
コメントで指摘したように、STEP4 の条件if(res == d1-> oldval2)は不要です。省略しても、*(d-> addr2)!= dのため、Complete()の両方のCAS1が失敗します。 。その唯一の目的は、関数呼び出しを避けることです。
例T1 = thread1、T2 = thread2:
remember that addr1 / addr2 are in a shared data zone !!!
T1 enter RDCSS function
T2 enter RDCSS function
T2 complete STEP1 (and store the pointer to its descriptor d2 in addr2)
T1 at STEP1 the CAS1 fails and res = d2
T2 or T1 completes *(d2->addr2)=d2->newval2 (suppose that *(d2->addr1)==d2->oldval1)
T1 execute STEP1 and now CAS1 can fail because *addr2 == d2->newval2
and maybe d2->newval2 != d1->oldval2, in every case at the end
res == d2->newval2 (fail) or
res == d1->oldval2 (success)
T1 at STEP2 skips the call to Complete() (because now res is not a descriptor)
T1 at STEP3 exits the loop (because now res is not a descriptor)
T1 at STEP4 T1 is ready to store d1->newval2 to addr2, but only if
*(d1->addr2)==d (we are working on our descriptor) and *(d1->addr1)==d1->oldval1
( Custom() function)