2017-05-04 51 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





重视。其他答案似乎对实现做出了太多的假设,其中没有一个是通过规范测试的。

相关问题