Perl 6可以保留多少个承诺?

时间:2017-04-17 10:41:38

标签: multithreading promise perl6

这是一个有点滑稽的标题,但在玩Promises时,我想知道我可以延伸这个想法。在这个程序中,我这样做,所以我可以指定我想要做多少个承诺。

  • 线程调度程序中的默认值是16个线程(rakudo/ThreadPoolScheduler.pm
  • 如果我指定的数字超过该数字,程序会挂起,但我没有收到警告(例如,“太多线程”)。
  • 如果我设置了RAKUDO_MAX_THREADS,我可以暂停程序,但最终会有太多的线程竞争无法运行。

我真的有两个问题。

  • 程序如何知道它可以生成多少个线程?这比承诺的数量略多,因为这是值得的。

  • 我怎么知道我应该允许多少线程,即使我可以赚更多?

这是我小小的Macbook Air上的Rakudo 2017.01,有4个核心:

my $threads = @*ARGS[0] // %*ENV<RAKUDO_MAX_THREADS> // 1;
put "There are $threads threads";

my $channel = Channel.new;

# start some promises
my @promises;
for 1 .. $threads {
    @promises.push: start {
        react {
            whenever $channel -> $i {
                say "Thread {$*THREAD.id} got $i";
                }
            }
        }
    }
put "Done making threads";

for ^100 { $channel.send( $_ ) }
put "Done sending";

$channel.close;

await |@promises;

put "Done!";

2 个答案:

答案 0 :(得分:11)

这实际上并不是Promise本身,而是关于线程池调度程序。 Promise本身只是一个同步构造。 start构造实际上做了两件事:

  1. 确保区块内有新的$_$/$!
  2. 使用该块调用Promise.start
  3. Promise.start也做了两件事:

    1. 创建并返回Promise
    2. 计划要在线程池上运行的块中的代码,并安排成功完成保留Promise并且异常中断Promise
    3. 不仅可能,而且相对常见的是,Promise对象不受线程池上的代码支持。 Promise.inPromise.anyofPromise.allof工厂没有立即安排任何事情,Promise的各种用途涉及做Promise.new然后调用稍后keepbreak。因此,我可以轻松地在1000 await s:

      上创建并Promise
      my @p = Promise.new xx 1000;
      start { sleep 1; .keep for @p };
      await @p;
      say 'done' # completes, no trouble
      

      同样,Promise并不是唯一可以在ThreadPoolScheduler上安排代码的东西。返回Supply的许多内容(如间隔,文件监视,异步套接字,异步进程)都会在那里安排回调。通过执行$*SCHEDULER.cue: { ... }可以将代码抛到一种即发即弃的风格(虽然通常你关心结果或任何错误,所以它并不是特别常见)。

      当前的Perl 6线程池调度程序具有可配置但强制执行的上限,默认为16个线程。如果您创建了一个所有16个都被占用但无法取得进展的情况,并且唯一可以取得进展的事情就是卡在工作队列中,那么就会发生死锁。这对于Perl 6线程池来说并不是唯一的。任何有界池都容易受到攻击(并且任何无界的池都很容易耗尽所有资源并使进程死亡: - ))。

      如另一篇文章中所述,Perl 6.d将使awaitreact非阻塞构造;这一直是计划,但没有足够的开发资源及时实现Perl 6.c. use v6.d.PREVIEW pragma提供对此功能的早期访问权限。 (另外,公平警告,这是一项正在进行的工作。)结果是,线程池拥有的线程上的awaitreact将暂停执行预定代码(对于那些好奇,通过继续)并允许线程继续进行进一步的工作。当等待的事情完成时,或者react块获得done时,将安排恢复代码。请注意,这意味着您可以在6.d中的awaitreact之前和之后使用不同的OS线程。 (大多数Perl 6用户不需要关心这一点。它主要与那些编写C语言绑定或处理系统的东西相关。而且一个好的C库绑定将使得绑定的用户没有照顾。)

      即将到来的6.d更改并没有消除耗尽线程池的可能性,但这意味着你可以在6.c中做的一系列方法将不再受到关注(注意,编写递归征服) /划分await分割部分的结果,或者用start react { ... }启动了数千个活动反应块的东西。

      展望未来,线程池调度程序本身也将变得更加智能。接下来是猜测,虽然我可能会实施这些变化,但这可能是最好的推测。 :-)线程池将在进行进度后开始,并使用它来动态调整池大小。这将包括注意到没有进展,并结合工作队列包含项目的观察,添加线程以尝试解决死锁 - 以增加线程的内存开销为代价。今天线程池保守地倾向于产生最大尺寸,即使这不是一个特别理想的选择;最有可能使用某种爬山算法来尝试确定最佳数量。一旦发生这种情况,可以大大提高默认的max_threads,这样就可以完成更多的程序 - 以一堆内存开销为代价 - 但大多数程序只运行一些线程。

答案 1 :(得分:6)

快速修复,在第一行添加function buildGraphNode(treeNode){ var memb; var child; if(!(treeNode.data instanceof Array)) { memb = member("" + treeNode.label, "" + treeNode.icon, '', '#d0000c', '', 'B','\uf16b' ); members.push(memb); for (var i = 0; i < treeNode.children.length; i++) { child = buildGraphNode(treeNode.children[i]); if (child != null && memb != null) { connections.push(link(memb, child)); } } return memb; } else{ for (var i = 0; i < treeNode.children.length; i++) { child = buildGraphNode(treeNode.children[i]); } } } 这解决了许多线程耗尽问题。

我添加了一些其他更改,例如use v6.d.PREVIEW;,并添加了Promise“id”,以便很容易看到Thread ID不一定与给定的Promise相关。

$*SCHEDULER.max_threads
#! /usr/bin/env perl6

use v6.d.PREVIEW; # <--

my $threads = @*ARGS[0] // $*SCHEDULER.max_threads;
put "There are $threads threads";

my $channel = Channel.new;

# start some promises
my @promises;
for 1 .. $threads {
    @promises.push: start {
        react {
            whenever $channel -> $i {
                say "Thread $*THREAD.id() ($_) got $i";
            }
        }
    }
}
put "Done making threads";

for ^100 { $channel.send( $_ ) }
put "Done sending";

$channel.close;

await @promises;

put "Done!";