我的前一篇博文中已经讲过利用Coro协程模块编写批量telnet的脚本。最近我随“大溜”去了,自学python和C++(学会了perl,融会贯通,学习python是件挺轻松的事情)。多亏了python、C++中文材料丰富,尤其是多线程、并发这一块,让我进一步搞懂了一些概念。并且可以融汇贯通到perl中。
闲话少说,下面讲讲协程、多线程和进程的区别:
一般来说,协程属于线程,线程属于进程。
详细讲,就是操作系统调集内存、cpu等资源生成一个进程,也就是说进程拥有操作系统分配给他的固定的cpu、内存等资源。进程同样也可以调集这部分内存、cpu等资源进行细分,而细分出来的小块内存、cpu资源就是线程。
线程与进程相比,主要优势在于进程分配内存、CPU资源形成线程的速度要比操作系统分配内存、cpu等资源形成进程的速度要快,即线程的开销比进程的开销要小。
多线程开销小,但不代表没有开销,也就是说有进一步压缩开销的空间。于是,就出现了在一个线程中,通过“见缝插针”的方式,由程序自身的控制,在遇见线程堵塞时,将CPU让给其他要执行的任务。通过空闲时间内让出CPU资源,来模拟多线程,即为协程。
“协”,协商的意思。
无论是perl还是python,都是在单核上利用锁机制(也就是控制任务使用CPU的时间,实现多线程)的。
多线程和协程间的主要区别在于,谁来决定任务占用CPU资源的时间。
多线程,根据python的相关资料,我推断(相关书籍和材料,以及各大论坛上大牛的讲解,均没有明言的)是通过定时切换的,也可以说是固定频率的,每一个线程得到CPU资源的时间是相同的,依次轮流。可以说,多线程是自动的,不需要程序员去编写特定代码的。
协程,由程序员设计各个任务间如何切换CPU资源。也就是说,需要程序员自己利用特定的协程关键字去敲代码实现的,例如使用async/await、yield、cede等关键字。
所以,无论是perl的coro还是python的gevent以及3.5版本之后的async/await,要想用协程,程序员自己写的部分和导入的模块都必须实现协程。这就解释了,为什么Coro在CPAN可用的包很少,而python中gevent要重写标准库中网络包。
前面已经说了,对于多线程的机制,是我的推断。如果推断成立,CPAN上随便一个包,都可以通过多线程进行并发计算。而telnet批量管理网络设备,也就不用搞得像Coro那么复杂,直接用Net::Telnet模块就好了。当然,最后证明推断是没错的。
perl的多线程,历史较短,而且前期稳定性不高,所以在大小骆驼以及豹书等perl的主要中文书籍中都没有提及,加之anyevent、coro等模块备受推崇,所以perl自带的threads模块(注意thread是老模块,threads是官方推荐的用于替代thread。就像python中的threading一般)很容易被忽视。
其实,threads和python中的threading同样简单易用。
文章最后,放出多线程telnet的实现代码,内含同步代码(注释中已写明),可以自行比较:
use strict;
use threads;
use Thread::Semaphore;
use Net::Telnet::Cisco;
my $start = time();
my @host = ("192.168.0.2","192.168.0.3","192.168.0.4","192.168.0.5","192.168.0.6");
my @threads;
my $count = 0;
my $signal = Thread::Semaphore->new(); #测试同步时,请注释掉此行
for my $host (@host) {
$threads[$count] = threads->create(\&switch,$host); #测试同步时,请注释掉此行
$count++; #测试同步时,请注释掉此行
#&switch($host); #测试同步时,请去掉注释!!!
}
for my $th (@threads) { #测试同步时,请注释掉此行
$th->join(); #测试同步时,请注释掉此行
} #测试同步时,请注释掉此行
my $endtime = time() - $start;
print "\n$endtime\n";
sub switch{
my $host = shift;
my $s = Net::Telnet::Cisco->new($host);
$s->login(Name => 'cisco',Password => 'cisco');
my @output = $s->cmd('show version');
$signal->down(); #测试同步时,请注释掉此行
print '*#*' x 30;
print @output;
print '=&=' x 30;
$signal->up(); #测试同步时,请注释掉此行
$s->close;
}