2017-05-04 68 views
6

是否安全,在承諾之間共享一個數組,像我在​​下面的代碼中做的那樣?在線程之間共享數組是否安全?

#!/usr/bin/env perl6 
use v6; 

sub my_sub ($string, $len) { 
    my ($s, $l); 
    if $string.chars > $len { 
     $s = $string.substr(0, $len); 
     $l = $len; 
    } 
    else { 
     $s = $string; 
     $l = $s.chars; 
    } 
    return $s, $l; 
} 

my @orig = <length substring character subroutine control elements now promise>; 
my $len = 7; 
my @copy; 
my @length; 
my $cores = 4; 
my $p = @orig.elems div $cores; 
my @vb = (0..^$cores).map: { [ $p * $_, $p * ($_ + 1) ] }; 
@vb[@vb.end][1] = @orig.elems; 

my @promise; 
for @vb -> $r { 
    @promise.push: start { 
     for $r[0]..^$r[1] -> $i { 
      (@copy[$i], @length[$i]) = my_sub(@orig[$i], $len); 
     } 
    }; 
} 
await @promise; 
+0

承諾的重點在於承諾返回某些東西,而不是有意從'start'語句前綴中返回任何有用的東西。 –

+0

但是'開始'確實只會返回一些東西。我對併發部分感興趣,並行運行代碼,以便我的CPU的所有內核都必須工作。 –

+1

我在說的是類似於拿起扳手,然後用它來敲擊指甲。哪個工作,...我猜。 –

回答

14

這取決於你如何定義「數組」和「分享」。就數組而言,有兩種情況需要分別考慮:

  • 固定大小的數組(聲明爲my @a[$size]);這包括具有固定尺寸的多維數組(例如my @a[$xs, $ys])。這些具有有趣的屬性,支持它們的內存永遠不必調整大小。
  • 動態數組(聲明my @a),按需增長。實際上,隨着時間的推移,它們實際上使用了大量的內存塊。

至於共享是,也有三種情況:

  • 多個線程觸摸陣列在它的一生,但只有一個人能在同一時間內觸摸它,因外殼一些併發控制機制或整體程序結構。在這種情況下,數組永遠不會在「使用數組的併發操作」的意義上共享,因此不可能有數據競爭。
  • 只讀,非懶惰的情況。這是多個併發操作訪問非惰性數組的地方,但只能讀取它。
  • 讀/寫的情況下(包括讀取實際上導致寫入,因爲數組已被分配的東西要求懶惰的評估;注意這對固定大小的數組永遠不會發生,因爲它們從不懶惰)。

然後,我們可以總結安全如下:

     | Fixed size  | Variable size | 
---------------------+----------------+---------------+ 
Read-only, non-lazy | Safe   | Safe   | 
Read/write or lazy | Safe *   | Not safe  | 

的*指示需要注意的是,雖然它是從視圖的Perl 6的點安全,當然,你必須確保你沒有做與相同指數相沖突的東西。因此,總而言之,固定大小的數組可以安全地共享和分配給來自不同線程的元素「沒問題」(但要小心虛假分享,這可能會讓你付出沉重的性能損失)。對於動態數組,只有在共享期間才能讀取它們,並且即使這樣,如果它們不是懶惰的(儘管給定的數組賦值主要是渴望的,但不可能達到這種情況意外地)。即使寫入不同的元素,由於日益增長的操作,也可能會導致數據丟失,崩潰或其他不良行爲。

因此,考慮到原始示例,我們看到my @copy;my @length;是動態數組,因此我們不能在併發操作中寫入它們。但是,發生這種情況,所以代碼可能不安全。

其他職位已經在這裏做了一個體面的工作指向更好的方向,但沒有釘牢血淋淋的細節。

5

只是有標有start聲明前綴返回的值,使Perl 6的可以爲您處理同步的代碼。這是該功能的重點。
然後您可以等待所有的承諾,並使用await聲明獲取所有結果。

my @promise = do for @vb -> $r { 

    start 

     do # to have the 「for」 block return its values 

     for $r[0]..^$r[1] -> $i { 
      $i, my_sub(@orig[$i], $len) 
     } 
} 

my @results = await @promise; 

for @results -> ($i,$copy,$len) { 
    @copy[$i] = $copy; 
    @length[$i] = $len; 
} 

start聲明前綴只是排序的切向相關的並行性。
當你使用它時,你會說:「我現在不需要這些結果,但可能會稍後」。

也就是說它返回一個Promise(異步)的原因,而不是一個Thread(併發)

運行時允許實際推遲運行的代碼,直到你最終要求的結果,即使如此,它可能只需在同一個線程中依次執行所有這些操作。

如果實現實際上這樣做,就可能造成類似僵局,如果你不是通過不斷調用它.status方法等待它從Planned改變KeptBroken輪詢Promise,然後纔要求它的結果。
這是缺省調度程序將在任何Promise代碼(如果它有任何備用線程)時開始工作的部分原因。


我推薦看jnthn的演講「Parallelism, Concurrency, and Asynchrony in Perl 6」
slides

+0

返回值(用於「await」),然後將值複製到正確的位置使其稍慢。此外,代碼更難以閱讀。我嘗試使用'Thread'接口;我沒有看到任何速度增益,並且它更低。 –

+0

@sid_com將它放入數組會導致它在循環過程之前等待。如果你只是把'await'放在'@ results'所在的'for'循環中,它應該在它們全部完成之前開始處理值。 –

4

這個答案適用於我對MoarVM瞭解的情況,不知道藝術的狀態是在JVM後端(或JavaScript的後端FWIW)的東西。

  • 從幾個線程讀取標量可以安全地完成。
  • 修改從多個線程標而不必擔心段錯誤來完成,但是你可能會錯過的更新:

    $ perl6 -e 'my $i = 0; await do for ^10 { start { $i++ for ^10000 } }; say $i' 46785

    這同樣適用於像陣列更復雜的數據結構

(如丟失值被推送)和哈希(丟失的鍵被添加)。

因此,如果您不介意缺少更新,則應該從多個線程更改共享數據結構。如果您不介意丟失更新,我認爲這是您通常需要的更新,您應該以@Zoffix Znet和@raiph的建議以不同方式設置算法。

-1





重視。其他答案似乎對實現做出了太多的假設,其中沒有一個是通過規範測試的。

相關問題