在线程之间共享数组是否安全?

时间:2017-05-04 12:25:15

标签: variables concurrency perl6

是否安全,在promises之间共享一个数组,就像我在下面的代码中一样?

#!/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;

4 个答案:

答案 0 :(得分: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;是动态数组,因此我们不能在并发操作中写入它们。但是,这种情况发生了,因此可以确定代码不安全。

此处的其他帖子在指向更好的方向方面做得不错,但没有一个能够指出血腥的细节。

答案 1 :(得分:6)

只需使用start语句前缀标记的代码返回值,以便Perl 6可以为您处理同步。这个特征的重点是什么 然后,您可以等待所有Promises,并使用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(并发)的原因

允许运行时延迟实际运行该代码,直到您最终要求结果,即使这样,它也可以在同一个线程中按顺序执行所有这些。

如果实现实际上是这样做的话,如果您通过不断调用Promise方法来等待它从Planned更改为.status来轮询Promise,则可能会导致类似死锁的行为KeptBroken,然后才会询问结果 这是默认调度程序开始处理任何“Parallelism, Concurrency, and Asynchrony in Perl 6”代码的部分原因,如果它有任何备用线程。

我建议观看jnthn的演讲slides JSON Spec - does the key have to be surrounded with quotes?

答案 2 :(得分:5)

这个答案适用于我对MoarVM情况的理解,不知道JVM后端(或Javascript后端fwiw)的现状是什么。

  • 可以安全地从多个线程中读取标量。
  • 可以在不必担心段错误的情况下修改多个线程中的标量,但您可能会错过更新:

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

同样适用于更复杂的数据结构,例如数组(例如,缺少值被推送)和哈希(缺少键被添加)。

因此,如果您不介意丢失更新,那么从多个线程更改共享数据结构应该可行。如果您确实介意缺少更新,我认为这是您通常想要的更新,您应该按照@Zoffix Znet和@raiph的建议,以不同的方式设置算法。

答案 3 :(得分:0)

没有





认真。其他答案似乎对实现做了太多假设,其中没有一个是由规范测试的。