Perl 6 中的并發(fā)

Concurrency

與大多數(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 時開始的計算。PromisePlanned 狀態(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 的方法,如 instart 將會做到這一點(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;
    }
}

可以使用方法 grepmap 分別過濾或轉(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è)置 donequit 選項來完成:

$supply.tap: { ... },
    done => { say 'Job is done.' },
    quit => {
        when X::MyApp::Error { say "App Error: ", $_.message }
    };

quit 塊的工作方式非常類似于 CATCH。 如果異常被標(biāo)記為由 whendefault 塊看到,那么異常會被捕獲并處理。 否則,異常繼續(xù)沿調(diào)用樹向上(即,與沒有設(shè)置 quit 時行為相同)。

如果你伴隨著 whenever 使用 react 或者 supply block 語法,你可以在你的 whenever 塊中添加 phasers 來處理來自 tapped supply 的 donequit 消息:

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è)置 donequit 相同。

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();
    }
}

可以使用通道代替前面描述的 wheneverreact 塊中的 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)錯誤分別從 stdoutstderr 方法中作為 Supply 對象提供,可以根據(jù)需要進(jìn)行分接。

如果要寫入程序的標(biāo)準(zhǔn)輸入,您可以給構(gòu)造函數(shù)提供 :w 副詞,并使用方法 write,printsay 在程序啟動后寫入打開的管道:

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,PromiseSupply 中找到的方法允許您明確地提供調(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,SupplyChannel)在需要時使用 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)代碼,以使共享容器不是必需的。

翻譯的不夠流暢, 大西瓜。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,619評論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,155評論 3 425
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,635評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,539評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,255評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,646評論 1 326
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,655評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,838評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,399評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,146評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,338評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,893評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,565評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,983評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,257評論 1 292
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,059評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,296評論 2 376

推薦閱讀更多精彩內(nèi)容