與大多數(shù)現(xiàn)代編程語言一樣,Perl 6 被設(shè)計為支持并發(fā)(允許多個事件同時發(fā)生)和異步編程(有時稱為事件驅(qū)動或反應(yīng)式編程 - 即程序某些部分的事件或變化可能會導(dǎo)致程序流異步地改變程序的其它部分)。
Perl的并發(fā)設(shè)計的目的是提供一個高級的,可組合的,一致的接口,而不管如下所述的虛擬機(jī)通過工具層怎樣為特定操作的系統(tǒng)來實現(xiàn)它。
此外,某些Perl的特性可以隱式地以異步的方式操作,所以為了確保這些特性可預(yù)測的互通,用戶代碼應(yīng)在可能情況下,避免較低層級的并發(fā)的 API(即線程和調(diào)度器),并使用高級接口。
High-level APIs
Promises
一個 Promise(在其他編程環(huán)境中也叫做 future)封裝了可能尚未完成的計算, 或者甚至在獲得 promise 時開始的計算。Promise
從 Planned
狀態(tài)開始, 要么導(dǎo)致一個 Kept
狀態(tài), 意為該 promise 已經(jīng)成功完成, 要么導(dǎo)致一個 Broken
狀態(tài), 意為該 promise 已經(jīng)失敗。 通常這就是用戶代碼需要以并發(fā)或異步方式操作的使用最多的功能。Planned, 計劃; Kept, 保持, Broken, 中斷。
my $p1 = Promise.new;
say $p1.status; # Planned
$p1.keep('result');
say $p1.status; # Kept
say $p1.result; # result
# (since it has been kept, a result is available!)
my $p2 = Promise.new;
$p2.break('oh no');
say $p2.status; # Broken
say $p2.result; # dies with "oh no", because the promise has been broken
Promise
通過組合獲取更強(qiáng)大的力量, 例如通過鏈接, 通常通過 then 方法:
my $promise1 = Promise.new();
my $promise2 = $promise1.then(
-> $v { say $v.result; "Second Result"}
);
$promise1.keep("First Result");
say $promise2.result; # First Result \n Second Result
這里 then 方法安排代碼(即圓括號中的閉包)在第一個 Promise 為 kept 或 broken 時執(zhí)行, 它自身返回一個新的 Promise, 這個新的 Promise 會在執(zhí)行代碼時與結(jié)果一塊保存。 (如果代碼執(zhí)行失敗則 broken ) keep
更改 promise 的狀態(tài)為 Kept, 并設(shè)置結(jié)果為位置參數(shù)。result
阻塞當(dāng)前執(zhí)行的線程直到那個 promise 變?yōu)?kept 或 broken, 如果它是 kept, 那么它會返回那個結(jié)果(那是傳遞給 keep
的值, ) 否則它會根據(jù)傳遞給 break
的值拋出一個異常。后者的行為使用如下代碼進(jìn)行闡述:
my $promise1 = Promise.new();
my $promise2 = $promise1.then(-> $v { say "Handled but : "; say $v.result});
$promise1.break("First Result");
try $promise2.result;
say $promise2.cause; # Handled but : \n First Result
當(dāng)它在原來的作為參數(shù)傳遞的 promise 上調(diào)用 result
方法時, 這里的 break
會導(dǎo)致 then
代碼塊拋出一個異常, 這隨后會導(dǎo)致第二個 promise 變?yōu)?broken, 在它的結(jié)果被接收時反過來引發(fā)一個異常。然后能從 cause
中訪問那個實際的 Exception 對象。如果那個 promise 還沒有變?yōu)?broken, 那么 cause
會引發(fā)一個 X::Promise::CauseOnlyValidOnBroken 異常。
Promise 也能按計劃在將來某個時間自動保留(kept):
my $promise1 = Promise.in(5);
my $promise2 = $promise1.then(-> $v { say $v.status; 'Second Result' });
say $promise2.result; # 5 秒后打印出: Kept\n Second Result
in 方法創(chuàng)建了一個新的 promise 并安排一個新的任務(wù)在不早于所提供的秒數(shù)時間內(nèi)在它身上調(diào)用 keep
, 返回一個新的 Promise 對象。
promises 的一個非常頻繁的用法是運(yùn)行一塊代碼, 并且一旦它成功地返回就 keep 那個 promise, 或者當(dāng)那塊代碼死掉時中斷(break)那個 promise。start 方法為此提供了一種快捷方式:
my $promise = Promise.start(
{ my $i = 0; for 1 .. 10 { $i += $_ }; $i}
);
say $promise.status; # Kept
say $promise.result; # 55
這里返回的 promise 的結(jié)果(result
)是從代碼返回的值。類似地, 如果那段代碼失敗了(那個 promise 也因此被中斷), 那么 cause
會成為拋出的那個 Exception 對象:
my $promise = Promise.start({ die "Broken Promise" });
try $promise.result; # Nil
say $promise.cause; # Broken Promise
# in block <unit> at <unknown file> line 1
這被認(rèn)為是通常需要的模式以至于它還提供了子例程形式:
my $promise = start {
my $i = 0;
for 1 .. 10 {
$i += $_
}
$i
}
my $result = await $promise;
say $result;
await
幾乎等價于在由 start
返回的 promise 對象身上調(diào)用 result
但是它也會接受一組 promises 并返回每個 promise 的結(jié)果:
my $p1 = start {
my $i = 0;
for 1 .. 10 {
$i += $_
}
$i
};
my $p2 = start {
my $i = 0;
for 1 .. 10 {
$i -= $_
}
$i
};
my @result = await $p1, $p2;
say @result; # 55 -55
除了 await
之外, 兩個類方法把幾個 Promise 對象合并到一個新的 promise 對象中: 當(dāng)所有原來的 promises 是 kept 或 broken 時, allof
返回一個 kept 狀態(tài)的 promise:
my $promise = Promise.allof(
Promise.in(2),
Promise.in(3)
);
await $promise;
say "All done"; # Should be not much more than three seconds later
并且當(dāng)原 promises 中的任何一個的狀態(tài)變?yōu)?kept 或 broken 時, anyof
返回將為 kept 的新 promise:
my $promise = Promise.anyof(
Promise.in(3),
Promise.in(8600)
);
await $promise;
say "All done"; # Should be about 3 seconds later
不同于 await
,然而如果不引用原來的 promise, 那么就訪問不了原來狀態(tài)為 kept 的 promise 的結(jié)果,因此當(dāng)任務(wù)的完成或其他方面對于消費(fèi)者來說比實際結(jié)果更重要時,或者當(dāng)通過其它方式收集結(jié)果時。 你可能,例如,您可以創(chuàng)建一個依賴的Promise,它會檢查每個原始的 promise:
my @promises;
for 1..5 -> $t {
push @promises, start {
sleep $t;
Bool.pick;
};
}
say await Promise.allof(@promises).then({ so all(@promises>>.result) });
如果所有的 promise 都保持為 True, 那么它會打印 True, 否則會打印 False。
如果你正在創(chuàng)建一個 promise,你打算保持或中斷自己,那么在你做之前, 你可能不想要任何可能會收到 promise 以無意(或否則)保持或中斷該 promise 的代碼。 為了這個目的,就有了方法 vow,它返回一個 Vow 對象,它成為 promise 能被保留或中斷的唯一機(jī)制。 如果試圖直接保持或斷開這個 Promise ,則會拋出 X::Promise::Vowed 異常,只要 vow
對象保持私有,那么 promise 的狀態(tài)就是安全的:
sub get_promise {
my $promise = Promise.new;
my $vow = $promise.vow;
Promise.in(10).then({$vow.keep});
$promise;
}
my $promise = get_promise();
# Will throw an exception
# "Access denied to keep/break this Promise; already vowed"
$promise.keep;
返回一個將被自動保存或斷開的 promise 的方法,如 in
或 start
將會做到這一點(diǎn),所以沒有必要這樣做。
Supplies
Supply 是異步數(shù)據(jù)流傳輸機(jī)制,其可以以類似于其他編程語言中的"事件"的方式同時由一個或多個消費(fèi)者消費(fèi),并且可以被視為開啟"事件驅(qū)動"或反應(yīng)式設(shè)計。
最簡單的是,Supply 是一個消息流,可以有多個通過方法 tap
創(chuàng)建的訂閱者,其數(shù)據(jù)項可以使用 emit
放置。
Supply 可以是現(xiàn)場的(live
)或按需的(on-demand
)。 現(xiàn)場(live
)供應(yīng)就像電視廣播:那些調(diào)入(收聽/收看)的人不會得到先前發(fā)出的值。 點(diǎn)播(on-demand
)廣播就像 Netflix:每個開始流式傳輸電影(點(diǎn)擊電源)的人,總是從頭開始(獲取所有的值),不管有多少人正在觀看它。 請注意,沒有為按需
供應(yīng)保留歷史記錄,而是為供應(yīng)的每次點(diǎn)擊運(yùn)行 supply
塊。
Netflix: 在線觀看電影的網(wǎng)站
實時供應(yīng)(live Supply
)由 Supplier 工廠創(chuàng)建,每個發(fā)出的值在添加時傳遞給所有活動的 tappers:
my $supplier = Supplier.new;
my $supply = $supplier.Supply;
$supply.tap( -> $v { say $v });
for 1 .. 10 {
$supplier.emit($_); # 1\n2\n3\n4\n5\n6\n7\n8\n9\n10
}
請注意,tap
在供應(yīng)商Supplier創(chuàng)建的 Supply 對象上調(diào)用,并且新值在供應(yīng)商Supplier上發(fā)出。
或者作為由 supply
關(guān)鍵字創(chuàng)建的按需供應(yīng) Supply:
my $supply = supply {
for 1 .. 10 {
emit($_);
}
}
$supply.tap( -> $v { say $v });
# 1\n2\n3\n4\n5\n6\n7\n8\n9\n10
在這種情況下,供應(yīng)塊中的代碼在每次供應(yīng)返回的供應(yīng)被竊取時執(zhí)行,如下所示:
my $supply = supply {
for 1 .. 10 {
emit($_);
}
}
$supply.tap( -> $v { say "First : $v" });
$supply.tap( -> $v { say "Second : $v" });
tap
方法返回一個 Tap 對象,它可以用來獲取關(guān)于 tap 的信息,并且當(dāng)我們不再對事件感興趣時關(guān)閉它:
my $supplier = Supplier.new;
my $supply = $supplier.Supply;
my $tap = $supply.tap( -> $v { say $v });
$supplier.emit("OK");
$tap.close;
$supplier.emit("Won't trigger the tap");
在供應(yīng)對象(supply object)上調(diào)用 done
調(diào)用可以為任何 tap 指定的 done
回調(diào),但不會阻止任何其他事件被發(fā)送到流,或者接收它們。
方法 interval
返回一個新的按需供應(yīng),它會以指定的間隔定期發(fā)出一個新事件。 發(fā)出的數(shù)據(jù)是從0開始的整數(shù),對于每個事件遞增。 以下代碼輸出 0 .. 5:
my $supply = Supply.interval(2);
$supply.tap(-> $v { say $v });
sleep 10;
這也可以使用 react
關(guān)鍵字書寫(輸出0..4):
react {
whenever Supply.interval(2) -> $v {
say $v;
done() if $v == 4;
}
}
這里,whenever 關(guān)鍵字使用 .act 從提供的塊在 Supply 上創(chuàng)建一個 tap。 當(dāng)在其中一個 tap 中調(diào)用 done()
時,退出 react
塊。
第二個參數(shù)可以提供給 interval
,它指定第一個事件觸發(fā)之前的延遲(以秒為單位)。 通過 interval
創(chuàng)建的 supply 的每個 tap 都有自0開始的自身序列,如下所示:
my $supply = Supply.interval(2);
$supply.tap(-> $v { say "First $v" });
sleep 6;
$supply.tap(-> $v { say "Second $v"});
sleep 10;
也可以從將要依次發(fā)出的值的列表中按需創(chuàng)建 Supply(供給),因此第一個按需示例(打印1到10)可以寫作:
react {
whenever Supply.from-list(1..10) -> $v {
say $v;
}
}
可以使用方法 grep
和 map
分別過濾或轉(zhuǎn)換現(xiàn)有的供應(yīng)對象(supply object),以類似具名列表方法的方式創(chuàng)建新供應(yīng)(supply):grep
返回這樣一個供應(yīng)(supply),以至于只有在源流上發(fā)出的那些事件的 grep
條件為真時才在第二個 supply 上發(fā)出:
my $supplier = Supplier.new;
my $supply = $supplier.Supply;
$supply.tap(-> $v { say "Original : $v" });
my $odd_supply = $supply.grep({ $_ % 2 });
$odd_supply.tap(-> $v { say "Odd : $v" });
my $even_supply = $supply.grep({ not $_ % 2 });
$even_supply.tap(-> $v { say "Even : $v" });
for 0 .. 10 {
$supplier.emit($_);
}
map
返回一個新的 supply(供應(yīng)),使得對于發(fā)送到原始供應(yīng)的每個項目,發(fā)出作為傳遞給 map
表達(dá)式的結(jié)果的新項目:
my $supplier = Supplier.new;
my $supply = $supplier.Supply;
$supply.tap(-> $v { say "Original : $v" });
my $half_supply = $supply.map({ $_ / 2 });
$half_supply.tap(-> $v { say "Half : $v" });
for 0 .. 10 {
$supplier.emit($_);
}
如果您需要在 supply(供應(yīng))完成時運(yùn)行一個操作,您可以通過在對 tap
的調(diào)用中設(shè)置 done
和 quit
選項來完成:
$supply.tap: { ... },
done => { say 'Job is done.' },
quit => {
when X::MyApp::Error { say "App Error: ", $_.message }
};
quit
塊的工作方式非常類似于 CATCH
。 如果異常被標(biāo)記為由 when
或 default
塊看到,那么異常會被捕獲并處理。 否則,異常繼續(xù)沿調(diào)用樹向上(即,與沒有設(shè)置 quit
時行為相同)。
如果你伴隨著 whenever
使用 react
或者 supply
block 語法,你可以在你的 whenever
塊中添加 phasers 來處理來自 tapped supply 的 done
和 quit
消息:
react {
whenever $supply {
...; # your usual supply tap code here
LAST { say 'Job is done.' }
QUIT { when X::MyApp::Error { say "App Error: ", $_.message } }
}
}
這里的行為與在 tap
上設(shè)置 done
和 quit
相同。
Channels
通道(Channel)是線程安全的隊列,可以具有多個讀取器和寫入器,可以被認(rèn)為在操作上與“fifo”(先進(jìn)先出)或命名管道相似,除了它不啟用進(jìn)程間通信之外。 應(yīng)該注意的是,作為真正的隊列,發(fā)送到通道的每個值將僅在先讀,先服務(wù)的基礎(chǔ)上對于單個讀取器可用:如果想要多個讀取器能夠接收可能想要發(fā)送的每個項目那么請考慮Supply。
項目(item)通過方法 send 排隊到通道上,方法 receive 從隊列中刪除一個項目并返回,如果隊列為空,則阻塞它直到發(fā)送新項目:
my $channel = Channel.new;
$channel.send('Channel One');
say $channel.receive; # 'Channel One'
如果使用 close 方法關(guān)閉了通道,那么任何發(fā)送(send
)都將導(dǎo)致拋出異常 X::Channel::SendOnClosed,并且如果隊列中沒有更多的項目,接收(receive
) 將拋出一個 X::Channel::ReceiveOnClosed 異常。
方法list返回通道上的所有項目,并將阻塞,直到其他項目被排隊,除非通道關(guān)閉:
my $channel = Channel.new;
await (^10).map: -> $r {
start {
sleep $r;
$channel.send($r);
}
}
$channel.close;
for $channel.list -> $r {
say $r;
}
還有從通道返回可用項目的非阻塞方法poll, 或者, 如果沒有項目或通道被關(guān)閉則返回 Nil,這當(dāng)然意味著必須檢查通道以確定其是否關(guān)閉:
my $c = Channel.new;
# Start three Promises that sleep for 1..3 seconds, and then
# send a value to our Channel
^3 .map: -> $v {
start {
sleep 3 - $v;
$c.send: "$v from thread {$*THREAD.id}";
}
}
# Wait 3 seconds before closing the channel
Promise.in(3).then: { $c.close }
# Continuously loop and poll the channel, until it's closed
my $is-closed = $c.closed;
loop {
if $c.poll -> $item {
say "$item received after {now - INIT now} seconds";
}
elsif $is-closed {
last;
}
say 'Doing some unrelated things...';
sleep .6;
}
# Doing some unrelated things...
# Doing some unrelated things...
# 2 from thread 5 received after 1.2063182 seconds
# Doing some unrelated things...
# Doing some unrelated things...
# 1 from thread 4 received after 2.41117376 seconds
# Doing some unrelated things...
# 0 from thread 3 received after 3.01364461 seconds
# Doing some unrelated things...
方法 closed 返回一個 Promise,當(dāng)通道關(guān)閉時,它將被保存(kept)(因此在布爾上下文中將被計算為 True)。
.poll
方法可以與 .receive
方法結(jié)合使用,作為一種緩存機(jī)制,其中 .poll
返回的值不足是需要獲取更多值并加載到通道的信號:
sub get-value {
return $c.poll // do { start replenish-cache; $c.receive };
}
sub replenish-cache {
for ^20 {
$c.send: $_ for slowly-fetch-a-thing();
}
}
可以使用通道代替前面描述的 whenever
和 react
塊中的 Supply:
my $channel = Channel.new;
my $p = start {
react {
whenever $channel {
say $_;
}
}
}
await (^10).map: -> $r {
start {
sleep $r;
$channel.send($r);
}
}
$channel.close;
await $p;
還可以使用通道方法從Supply獲得通道,該通道方法返回通過 Supply 上的 tap
饋送的通道:
my $supplier = Supplier.new;
my $supply = $supplier.Supply;
my $channel = $supply.Channel;
my $p = start {
react {
whenever $channel -> $item {
say "via Channel: $item";
}
}
}
await (^10).map: -> $r {
start {
sleep $r;
$supplier.emit($r);
}
}
$supplier.done;
await $p;
Channel
將返回一個不同的通道,每次調(diào)用時都會使用相同的數(shù)據(jù)。 這可以用于例如將 Supply 輸出到一個或多個通道以在程序中提供的不同接口。
Proc::Async
Proc::Async 構(gòu)建在所描述的設(shè)施上以異步方式運(yùn)行并與外部程序交互:
my $proc = Proc::Async.new('echo', 'foo', 'bar');
$proc.stdout.tap(-> $v { print "Output: $v" });
$proc.stderr.tap(-> $v { print "Error: $v" });
say "Starting...";
my $promise = $proc.start;
await $promise;
say "Done.";
# Output:
# Starting...
# Output: foo bar
# Done.
命令的路徑以及命令的任何參數(shù)都提供給該構(gòu)造函數(shù)。 該命令將不被執(zhí)行,直到調(diào)用 start,它將返回一個 Promise,當(dāng)程序退出時該 Promise 變?yōu)?kept 狀態(tài)。 程序的標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤分別從 stdout 和 stderr 方法中作為 Supply 對象提供,可以根據(jù)需要進(jìn)行分接。
如果要寫入程序的標(biāo)準(zhǔn)輸入,您可以給構(gòu)造函數(shù)提供 :w
副詞,并使用方法 write,print 或 say 在程序啟動后寫入打開的管道:
my $proc = Proc::Async.new(:w, 'grep', 'foo');
$proc.stdout.tap(-> $v { print "Output: $v" });
say "Starting...";
my $promise = $proc.start;
$proc.say("this line has foo");
$proc.say("this one doesn't");
$proc.close-stdin;
await $promise;
say "Done.";
# Output:
# Starting...
# Output: this line has foo
# Done.
一些程序(例如本例中沒有文件參數(shù)的 grep
)在關(guān)閉標(biāo)準(zhǔn)輸入之前不會退出,因此在完成寫入后可以調(diào)用 close-stdin,以允許由 start
返回的 Promise 的狀態(tài)變?yōu)?kept。
Low-level APIs
Threads
最低級別的并發(fā)接口由 Thread 提供。 線程可以被認(rèn)為是可以最終在處理器上運(yùn)行的一段代碼,其布置幾乎完全由虛擬機(jī)和/或操作系統(tǒng)完成。 線程應(yīng)該被考慮,對于所有意圖,很大程度上是不受管理的,應(yīng)避免在用戶代碼中直接使用它們。
線程可以被創(chuàng)建,然后隨后實際運(yùn)行:
my $thread = Thread.new(code => { for 1 .. 10 -> $v { say $v }});
# ...
$thread.run;
或者可以在單個調(diào)用中創(chuàng)建和運(yùn)行:
my $thread = Thread.start({ for 1 .. 10 -> $v { say $v }});
在這兩種情況下,由 Thread 對象封裝的代碼的完成可以用 finish
方法來等待,該方法將阻塞直到線程完成:
$thread.finish;
除此之外,沒有用于同步或資源共享的其他設(shè)施,這主要是為什么應(yīng)當(dāng)強(qiáng)調(diào)線程不可能直接用于用戶代碼。
Schedulers
并發(fā) API 的下一級由實現(xiàn)角色Scheduler定義的接口的類提供。 調(diào)度程序接口的目的是提供一種機(jī)制來確定使用哪些資源來運(yùn)行特定任務(wù)以及何時運(yùn)行它。 大多數(shù)較高級別的并發(fā) API 是基于調(diào)度器構(gòu)建的,并且用戶代碼根本不需要使用它們,盡管一些方法,例如在 Proc::Async,Promise 和 Supply 中找到的方法允許您明確地提供調(diào)度器。
當(dāng)前缺省全局調(diào)度程序在變量 $*SCHEDULER
中可用。
調(diào)度程序的主接口(確實是Scheduler接口所需的唯一方法)是 cue
方法:
method cue(:&code, Instant :$at, :$in, :$every, :$times = 1; :&catch)
這將按照由副詞(如在Scheduler中記錄的)所確定的方式使用由調(diào)度器實現(xiàn)的執(zhí)行方案來調(diào)度 &code
中的 Callable
以執(zhí)行。 例如:
my $i = 0;
my $cancellation = $*SCHEDULER.cue({ say $i++}, every => 2 );
sleep 20;
假設(shè) $*SCHEDULER
沒有從默認(rèn)值改變,將以大約每兩秒打印數(shù)字0到10(即使用操作系統(tǒng)調(diào)度容差)。 在這種情況下,代碼將被調(diào)度運(yùn)行,直到程序正常結(jié)束,但是該方法返回一個 Cancellation 對象,它可以用來在正常完成之前取消調(diào)度執(zhí)行:
my $i = 0;
my $cancellation = $*SCHEDULER.cue({ say $i++}, every => 2 );
sleep 10;
$cancellation.cancel;
sleep 10;
應(yīng)該只輸出 0 到 5,
盡管 Scheduler 接口提供的所有功能明顯優(yōu)于 Thread 提供的,但是通過更高級別的接口可以獲得所有的功能,并且不應(yīng)該有必要直接使用調(diào)度器,除非在上述情況下,調(diào)度器可以被明確地提供給某些方法。
如果庫具有特殊要求,例如 UI 庫可能希望所有代碼在單個 UI 線程中運(yùn)行,或者可能需要一些定制的優(yōu)先級機(jī)制,則庫可能希望提供備選的調(diào)度器實現(xiàn),然而,被作為標(biāo)準(zhǔn)的實現(xiàn)和下面的描述應(yīng)該足以滿足大多數(shù)用戶代碼。
ThreadPoolScheduler
ThreadPoolScheduler 是默認(rèn)調(diào)度程序,它維護(hù)一個根據(jù)需要分配的線程池,根據(jù)需要創(chuàng)建新的線程,直到創(chuàng)建調(diào)度程序?qū)ο髸r作為參數(shù)給出的最大數(shù)目(默認(rèn)值為16)。如果超過最大值 那么 cue
可以對代碼進(jìn)行排隊,直到線程變得可用為止。
Rakudo 允許在程序啟動時由環(huán)境變量 RAKUDO_MAX_THREADS
在默認(rèn)調(diào)度程序中設(shè)置允許的最大線程數(shù)。
CurrentThreadScheduler
CurrentThreadScheduler 是一個非常簡單的調(diào)度程序,它將始終調(diào)度代碼在當(dāng)前線程上立即運(yùn)行。 暗示這個調(diào)度器的 cue
將阻塞,直到代碼完成執(zhí)行,把它的效用限制在某些特殊情況,如測試。
Locks
類 Lock 在并發(fā)環(huán)境中提供了保護(hù)共享數(shù)據(jù)的低級機(jī)制,并因此是高級 API 中支持線程安全性的關(guān)鍵,這在其他編程語言中有時稱為 “Mutex”。 因為較高級別的類(Promise,Supply 和 Channel)在需要時使用 Lock,所以用戶代碼不可能直接使用 Lock。
Lock 的主接口是方法 protect,它確保一個代碼塊(通常稱為“臨界區(qū)”)只能在一個線程中同時執(zhí)行:
my $lock = Lock.new;
my $a = 0;
await (^10).map: {
start {
$lock.protect({
my $r = rand;
sleep $r;
$a++;
});
}
}
say $a; # 10
protect
返回代碼塊返回任何東西。
因為 protect
將阻止任何等著要執(zhí)行臨界區(qū)的線程,所以代碼應(yīng)該盡可能快。
Safety Concerns
一些共享數(shù)據(jù)并發(fā)問題相比其他問題并不明顯。 關(guān)于這個問題的好文章請看這個博客。
要注意的一個特別的問題是當(dāng)容器自動更新或發(fā)生擴(kuò)展時。 當(dāng)數(shù)組或哈希條目被賦初始值時,底層結(jié)構(gòu)被更改,并且那個操作不是異步安全的。 例如,在這段代碼中:
my @array;
my $slot := @array[20];
$slot = 'foo';
第三行是臨界區(qū),因為那就是數(shù)組被擴(kuò)展之時。 最簡單的解決方法是使用 <Lock>
來保護(hù)臨界區(qū)。 一個可能更好的解決方案是重構(gòu)代碼,以使共享容器不是必需的。
翻譯的不夠流暢, 大西瓜。