Multithreading 线程、Coro、任意事件混淆
我对perl比较陌生,甚至对perl中的线程技术也比较陌生。我有一个perl脚本,它从3个不同的源获取输入。(2个LDAP查询和一个不总是存在的文件)因为某些部分可能需要比其他部分更长的时间,所以我决定使用线程和队列。在开发过程中,测试脚本的各个组件效果很好,但将它们放在一起之后,性能似乎会下降 基本结构是这样的 2个线程:(读取文件或读取AD条目)->Queue1->2个线程:(清理数据)->Queue2->3-4个线程(与现有本地LDAP条目进行比较)。几个线程将统计信息报告回主脚本,一旦所有线程都完成,就会发送一封包含该运行的所有统计信息和状态的电子邮件 我正在使用dequeue_nb,我认为这会有所帮助,但没有运气 对性能的冲击似乎在排队等候。在寻找提高性能的技巧时,我读了几篇文章,说perl线程不好,并且使用coro、POE、Anyevent、IO:async等Multithreading 线程、Coro、任意事件混淆,multithreading,perl,coroutine,anyevent,Multithreading,Perl,Coroutine,Anyevent,我对perl比较陌生,甚至对perl中的线程技术也比较陌生。我有一个perl脚本,它从3个不同的源获取输入。(2个LDAP查询和一个不总是存在的文件)因为某些部分可能需要比其他部分更长的时间,所以我决定使用线程和队列。在开发过程中,测试脚本的各个组件效果很好,但将它们放在一起之后,性能似乎会下降 基本结构是这样的 2个线程:(读取文件或读取AD条目)->Queue1->2个线程:(清理数据)->Queue2->3-4个线程(与现有本地LDAP条目进行比较)。几个线程将统计信息报告回主脚本,一旦所
这看起来不像是一个“事件”问题,所以我不认为任何事件或POE会是我所看到的方式,coros似乎一次只使用一个CPU,所以我也不确定这是否可行。我想用它们的组合,但后来我的头开始痛了。是否有人对如何修复/排除我的脚本或如何实现其他模块有任何建议 并行性的一个问题是同步。这是一个性能杀手,它很糟糕,如果可能的话应该避免 操作系统架构 让我们看看您的体系结构:
+--------------+--------------+
| Input 1 | Input 2 |
+--------------+--------------+
| QUEUE A |
+--------------+--------------+
| Scrub 1 | Scrub 2 |
+--------------+--------------+
| QUEUE B |
+---------+---------+---------+
| Compare | Compare | Compare |
+---------+---------+---------+
讨论
队列A必须跨四个线程同步;B队穿过5-6号线。任何时候只有一个线程可以访问队列,因此大部分时间线程都在等待,无法工作
并行流水线结构
稍微不同的体系结构可能如下所示:
+-----------+ +-----------+
| Input 1 | | Input 2 |
+-----------+ +-----------+
| QUEUE 1A | | QUEUE 2A |
+-----------+ +-----------+
| Scrub 1 | | Scrub 2 |
+-----------+ +-----------+
| QUEUE 1B | | QUEUE 2B |
+-----+-----+ +-----+-----+
| Cmp | Cmp | | Cmp | Cmp |
+-----+-----+ +-----+-----+
讨论
这里,A队列仅与两个线程相关(->更少等待),B队列仅与三个线程相关。对于类似的输入大小/复杂度,此体系结构应该执行得更快。如果输入2短得多,整个管道2将在管道1完成一半之前运行。但是,它比为每个管道使用单个进程要好得多
草坪喷灌建筑
概念
更好的体系结构将尝试将流程的输出分布在多个队列中。(反之,当队列为空时,让线程从多个队列获取其输入是错误的。)
每个队列写入都应转到不同的队列:
+-----------+ +-----------+
| Input 1 | | Input 2 |
+-----------+ +-----------+
| \ / |
+-----------+ +-----------+
| QUEUE 1A | | QUEUE 2A |
+-----------+ +-----------+
| Scrub 1 | | Scrub 2 |
+-----------+ +-----------+
/ | \ \ / / | \
+-------+-------+-------+-------+
| Q. 1B | Q. 2B | Q. 3B | Q. 4B |
+-------+-------+-------+-------+
| Cmp | Cmp | Cmp | Cmp |
+-------+-------+-------+-------+
这可以确保每个线程具有相同的工作负载,但不能确保所有线程同时完成
讨论
所有队列在3个线程之间共享。问题是两个线程在写入队列时会互相阻塞。如果队列写入访问之间的时间明显大于写入持续时间,这应该没有问题,否则可以混合使用第二种体系结构
因此,这种架构是否合理取决于您的确切需求
对于大小均匀的输入,它的速度较慢,但对于不规则的输入,它的性能更好
附录
关于实施:
所使用的框架是体系结构的次要部分。如果只传递文本字符串,我强烈建议使用管道。如果必须传递Perl数据类型或对象,则可能必须接受使用真实队列的额外开销:在向队列添加非共享变量时,除了所有同步开销外,还必须进行深度复制(请参见@Leon Timmermans answer)
关于可伸缩性:
体系结构1和3在线程数量上不是固定的。我强烈建议使用这种灵活性对不同的组合进行基准测试。经验法则是,应该使用n到2n个线程,其中n是处理器(或硬件线程)的数量。这可以看作是一个阶段中线程的最大合理数量。除此之外,你只会得到一个记忆惩罚,而不会加速。性能饱和点可能会提前达到,此时阶段处理输入的速度可能比提供的速度快。您将什么类型的数据放入队列?AFAIK简单数据比复杂结构便宜,因为它至少需要克隆和复制两次。我一直计划编写一个更快的队列实现(实际上大部分工作已经完成),但还没有发布。对我来说,Perl中的多线程通常不值得这么麻烦;有时我使用fork()来实现并发性。实现任务的一种方法是为每个查询/输入创建一个新流程,并将结果写入文件。之后,在主进程中对数据进行munge。@amon,Forking强制使用管道,这很好。问题是,他(说他)已经在使用管道了。分叉不会改变任何事情。谢谢阿蒙的回复。我尝试过草坪洒水器的方法,但它似乎对性能没有任何帮助。从长远来看,它可能会受到一些伤害。因为这是一个对时间敏感的修复,我已经把我的产品投入生产,但只是改变了一些事情的时间安排。不过我会继续看的。由于磨砂阶段似乎非常快,而且代码相对较小,我正在考虑将其与最后一步结合起来。也许这会有帮助。第一个队列正在接收数组。第一个值是数据的源,它还告诉清理线程第二个值是什么类型的数据。第二个值是长字符串或LDAP条目的副本。一旦你有了一些(半)工作代码,我很想尝试一下。也许还不是为了制作,但也许是为了帮助你完成它。