在 Perl6 脚本中并发执行 ssh 命令

Thread 示例
说起来 Perl6 近年一直在宣传 Promise 啊,Supply 啊并发编程,但是 API 变化太快,2013 年中期 jnthn 演讲里演示的 async 用法,现在就直接报这个函数不存在了,似乎改成 start 了?天知道什么时候又变。所以还是用底层的 Thread 和 Channel 来写。话说其实这还是我第一次写 Thread 呢。

 代码如下 复制代码
use v6;
class OpenSSH {
    has $!user = 'root';
    has $!port = 22;
    has $!ssh  = "ssh -oStrictHostKeyChecking=no -l{$!user} -p{$!port} ";
    multi method exec($host, $cmd) {
        my $out;
        my $shell = $!ssh ~ $host ~ ' ' ~ $cmd;
        try { $out = qqx{ $shell }.chomp }
        CATCH { note("Failed: $!") };
        return $out;
    }
    multi method exec(@hosts, $cmd) {
        my $c = Channel.new;
        my @t = @hosts.map({
            Thread.start({
                my $r = $.exec($_, $cmd);
                $c.send($r);
            })
        });
        @t>>.finish;
        return @hosts.map: { $c.receive };
    }
}
my $ssh = OpenSSH.new(user => 'root');
say $ssh.exec('10.4.1.21', 'uptime');
my @hosts = '10.4.1.21' xx 5;
my @ret = $ssh.exec(@hosts, 'sleep 3;echo $$');
say @ret.perl;

很简陋的代码。首先一个是要确认 ssh 不用密码登陆,因为没有写 Expect;其次是没用 ThreadPool,所以并发操作不能太猛,会扭着腰的。

这里演示了几个地方:

class 的定义和 attr 的定义和用法
try-catch 的用法

也可以不写 try,直接 CATCH {}

qqx{} 的用法

这是变动比较大的地方,qqx 后面只能用 {} 不能用其他字符对了。Perl6 提供另外的 shell() 指令,返回 Proc::Status 对象。 不过这个对象其实也就是个状态码,不包括标准输出、错误输出什么的。

字符串连接符 ~ 的用法
multi method 的定义和用法
函数 signature 的定义和用法,可选参数和命名参数的定义和用法见下一小节。
>> 操作符的用法

这里其实相当于是 .finish for @t。这个怪怪的操作符据说可以在可能的时候自动线程化数组操作,所以返回顺序不会跟.map一样。

xx 操作符的用法

Perl5 里有 x 操作符,Perl6 里又增加了 xx、 X 和 Z 等操作符。 分别是字符扩展成数组、数组扩展成多维数组和多数组压缩单个数组(也就是zip操作)。

Channel 和 Thread 对象的用法

在 roast 测试集里,只有 thread 和 lock 的测试用例。 semaphore 其实也支持(因为 MoarVM 是基于 libuv 的嘛,libuv 支持它当然也支持),但是连测试用例都没写……

默认的并发编程会采用 ThreadPoolScheduler 类,稍微看了一下,默认设置的线程数是 16。考虑下一步是仿照该类完善我的小脚本呢,还是重新学习一下 Supply 或者 Promise 看看到底怎么用。

有兴趣用 libssh2 的童鞋,可以学习一下 NativeCall 的用法。

ThreadPoolScheduler 示例
根据 S17-concurrency 文档 的内容,改写了几行脚本,实现了 ThreadPool 的效果:

 代码如下 复制代码
    multi method exec(@hosts, $cmd, :$parallel = 16) {
        my $c = Channel.new;
        my $s = ThreadPoolScheduler.new(max_threads => $parallel);
        @hosts.map({
            $s.cue({
                my $r = $.exec($_, $cmd);
                $c.send($r);
            })
        });
        return @hosts.map: { $c.receive };
    }

这里把默认并发值改成了 16,跟 Rakudo 保持一致。如果不需要可调的话,这里其实可以直接写成 $*SCHEDULER.cue({})。

然后调用方法也对应修改一下,考虑到辨识度,把并发值改成了命名参数。调用方法如下:

 代码如下 复制代码
my @hosts = slurp('iplist.txt').lines;
my @ret = $ssh.exec(@hosts, 'sleep 3;echo $$', :parallel(5));

运行可以看到,虽然 iplist.txt 里放了 40 个ip,但是并发的 ssh 只有 5 个。

Promise 示例
继续,S17 内容下一节是 Promise,之前博客里已经提过几次 Perl5 的 Promises 模块 或者类似的东西(比如 Mojo::IOLoop::Delay ),包括 JavaScript 等也有一样的名字。

不过 Perl5 的 Promises 思路参照的是 Scala,语法则偏向 nodejs 和 golang(都用一个叫 defer 的指令来创建 Promises 对象),写起来跟 Perl6 的原生 Promise 差距较大。

考虑 ssh 这个场景可能不太用的上 Promise 的 .in、.then、.anyof 之类的流程控制(尤其 .in 这个还不一定能用,因为 Promise 底层也是用的 $*SCHEDULER.cue(),而这个在 MoarVM 上目前还不支持 :in/:at/:every 等参数),就直接展示最简单的并发了:

 代码如下 复制代码
    multi method exec(@hosts, $cmd, :$parallel = 16) {
        $*SCHEDULER = ThreadPoolScheduler.new(max_threads => $parallel);
        await @hosts.map: {
            start {
                $.exec($_, $cmd);
            };
        };
    }

简单来说,就是每个 start {&c} 创建一个 Promise 对象,根据 &c 的返回值自动作 $p.keep($result) 或 $p.break(Exception)。然后 await(*@p) 回收全部 Promise 的结果。

这里直接修改了 $*SCHEDULER ,这是一个全局变量,即当前进程的调度方式。Promise 类默认就采用这个变量。如果想跟上一小节一样使用 $s,那这里就不能用 start {} 而是要用 Promise.start({}, $s)。显然写起来不怎么漂亮。

Supply 示例
Supply 是响应式编程,类似 Java 里的 Reactive 概念。应该适合的是一件事情多个进程重复做。场景不太对,二来目前 S17 也不全,就不写了。

时间: 2024-09-26 20:48:11

在 Perl6 脚本中并发执行 ssh 命令的相关文章

JAVA中如何执行DOS命令

  下面是一种比较典型的程序模式: ... Process process = Runtime.getRuntime().exec(".p.exe"); process.waitfor( ); ... 在上面的程序中,第一行的".p.exe"是要执行的程序 名,Runtime.getRuntime()返回当前应用程序的Runtime对象,该对象的exec()方法指示Java虚拟机创建一个子进程执行指 定的可执行程序,并返回与该子进程对应的Process对象实例.通过

Ruby中调用执行shell命令的6种方法_ruby专题

碰到需要调用操作系统shell命令的时候,Ruby为我们提供了六种完成任务的方法: 1.Exec方法: Kernel#exec方法通过调用指定的命令取代当前进程例子: 复制代码 代码如下:       $ irb       >> exec 'echo "hello $HOSTNAME"'          hello nate.local       $ 值得注意的是,exec方法用echo命令来取代了irb进程从而退出了irb.主要的缺点是,你无法从你的ruby脚本里知

shell中嵌套执行expect命令实例_linux shell

一直都想把expect的操作写到bash脚本里,这样就不用我再写两个脚本来执行了,搞了一下午终于有点小成就,给大家看看吧. 系统:centos 5.x 1.先安装expect 复制代码 代码如下: yum -y install expect 2.脚本内容: 复制代码 代码如下: cat auto_svn.sh #!/bin/bash passwd='123456' /usr/bin/expect <<-EOF set time 30 spawn ssh -p18330 root@192.168

linux中sbt 中单元测试并发执行例子

此次研究的目的原本是要使得 Play Framwork 2 中单元测试能够并发执行, 包括 JUnit 和 Spec 的测试用例, Play 2 的 activator 就是一个 sbt 的包装. 开发中发现我们 Play 2 中的单元测试是按序执行的, 实际上 sbt 下测试用例默认是并发执行的. 之所以 Play 2 的单元测试是按序的, 是因为 activator 设置了把 sbt 的两个属性 fork in Test := true 和 parallelExecution in Test

Java程序执行Linux命令

java程序中要执行linux命令主要依赖2个类:Process和Runtime 首先看一下Process类: [plain] view plaincopyprint? ProcessBuilder.start() 和 Runtime.exec 方法创建一个本机进程,并返回 Process 子类的一个实例,   该实例可用来控制进程并获得相关信息.Process 类提供了执行从进程输入.执行输出到进程.等待进程完成.   检查进程的退出状态以及销毁(杀掉)进程的方法.   创建进程的方法可能无法

服务器-linux下root用户切换到普通用户执行ssh远程登录失败

问题描述 linux下root用户切换到普通用户执行ssh远程登录失败 这样,我想做一个web一件抓取日志的小程序,后台python调用ssh登录到远程主机执行系统命令抓取符合条件的日志,这个程序集成在一个运行在root下的web站中,然而服务器 设定了root用户不能用ssh远程登录.我想请教是否可能在程序中切换到普通用户登录ssh远程登录其他主机执行ssh命令,然后再切回root...或者是否有其他好的方法来实现这一 功能.(很多台服务器,一次性抓取符合条件的日志) 解决方案 linux下如

oracle 中常见的set命令

当管理的数据库比较多的时候,在sqlplus里切换就是一件麻烦的事.要非常小心,以防出错.可以修改sqlplus的提示符:SQL>,把这个改成我们用户和实例名,这样就不容易出错. 先看一下Oracle自定义的参数: SQL>define DEFINE _DATE = "11-MAY-11" (CHAR) DEFINE _CONNECT_IDENTIFIER = "dave1" (CHAR) DEFINE _USER = "SYS" (

《R语言初学指南》一2.3 找到R脚本中的错误

2.3 找到R脚本中的错误 R语言初学指南复杂项目中的R脚本会非常长.即使是R专家,也很少能一次性将其编写正确.脚本中的所有错误都是通过调试来修改的. 在脚本中称追踪错误或"bug"为"调试".调试包括一些适用性检测工作. 下面在脚本中故意制造一个bug,由此来练习如何调试.首先来做一些清理工作.使控制台成为活动窗口,并输入下列命令: >objects() 按照惯例,这里在展示控制台中的命令时,依然在其前面显示R提示符(">"),但要

windows/linux中shell自动登录ssh并执行一些命令

ssh安全的自动登录 A为本地主机(即用于控制其他主机的机器) ; B为远程主机(即被控制的机器Server), 假如ip为172.24.253.2 ; A和B的系统都是Linux 在A上的命令:  代码如下 复制代码 # ssh-keygen -t rsa (连续三次回车,即在本地生成了公钥和私钥,不设置密码) # ssh root@172.24.253.2 "mkdir .ssh;chmod 0700 .ssh" (需要输入密码) # scp ~/.ssh/id_rsa.pub r