perl多进程实战之二

来源:互联网 发布:mac怎么连网线 编辑:程序博客网 时间:2024/05/17 17:39
每个进程都独立拥有自己的资源,包括内存页、文件指针等等,同时,linux系统提供了多种进程之间的交互方式,比较简单的方式就是写到文件里,DB_File就是一种解决方案。 
  
#!/usr/bin/perl
# test create of DB_File
# create by lianming: 2009-08-10
# last modify by lianming: 2009-08-13 
  
use strict;
use warnings;
use DB_File; 
  

my %hash;
my $file_name = "test_btree";
unlink $file_name; 
  
tie(%hash, 'DB_File', $file_name, O_CREAT|O_RDWR, 0666, $DB_BTREE)
    || die "Cannot open $file_name: $!\n"; 
  

## == Add some info ==
$hash{"me"} = "lianming";
$hash{"else"} = "nothing";
$hash{"job"} = "monitor"; 
  
print "Print as hash:\n";
foreach my $key (keys (%hash)) {
        print "$key->$hash{$key}\n";
}
print "End\n"; 
  
untie %hash; 
  
    执行结果为: 
  
Print as hash:
else->nothing
job->monitor
me->lianming
End 
  
    tie命令,本来是用于变量绑定,在DB_File中,是将一个哈希变量,和一个文件绑在了一起,简单的来讲,我们可以认为变量'%hash',是存在这个文件中的,所以我们对这个哈希变量进行的所有修改,它都会在文件中做相应的操作。 
    tie的参数,第一个为要绑定的变量 
        第二个为要绑定的文件 
        第三个为flag(是否创建,只读,只写,可读写) 
        第四个为权限(和linux文件的权限类似,不过没有执行,最大是6) 
        第五个为存储引擎,有三种,hash、btree、recno,一般来讲,hash和btree是存储key/value类型数据的,而recno是顺序存储,相当于数组。使用上的区别,请看下文。 
  
    在DB_File中添加信息,只要直接给哈希变量添加信息即可,和一般的赋值是一样的。 
    DB_File也提供了类似BerkeleyDB的方法来添加对象,要将tie的结果返回给一个值,例如$db,然后利用$db->put($key, $value)的方式来添加,这种办法我不是很习惯用。 
  
    同理,在DB_File中读取信息,就直接从哈希变量中读取就ok了。 
  
    这样,我们就已经把信息存在了文件中,下次,如果另外一个进程要来修改这个文件,直接打开即可。 
  
#!/usr/bin/perl
# test read/write of DB_File
# create by lianming: 2009-08-10
# last modify by lianming: 2009-08-10 
  
use strict;
use warnings;
use DB_File; 
  

my %hash;
my $file_name = "test_btree"; 
  
tie(%hash, 'DB_File', $file_name, O_WRONLY, 0666, $DB_BTREE)
    || die "Cannot open $file_name: $!\n"; 
  

## == Add some info ==
for (my $i = 0; $i < 10; $i ++) {
        $hash{$i} = $i."new";

  
print "Print as hash:\n";
foreach my $key (keys (%hash)) {
        print "$key->$hash{$key}\n";
}
print "End\n"; 
  
untie %hash; 
  
    执行结果如下: 
  
Print as hash:
0->0new
1->1new
2->2new
3->3new
4->4new
5->5new
6->6new
7->7new
8->8new
9->9new
else->nothing
job->monitor
me->lianming
End 
  
    只要记住,打开文件的时候要和创建的时候采用同样的存储算法,否则会报无法打开文件的错
一般来讲,哈希是不允许存储重复键的,就是说,对同一个key,赋两次value,那么后一次赋值会将前一次赋的值给覆盖掉,但是btree是可以存储这样的信息的。 
  
#!/usr/bin/perl
# test duplicate keys of DB_File
# create by lianming: 2009-08-10
# last modify by lianming: 2009-08-10 
  
use strict;
use warnings;
use DB_File; 
  

my %hash;
my $file_name = "test_btree";
unlink $file_name; 
  
# == enable duplicate records
$DB_BTREE->{'flags'} = R_DUP; 
  
tie(%hash, 'DB_File', $file_name, O_CREAT|O_WRONLY, 0666, $DB_BTREE)
    || die "Cannot open $file_name: $!\n"; 
  

## == Add some info ==
$hash{'me'} = "Sangyb";
$hash{'me'} = "Lianming";
$hash{'me'} = "Here"; 
  
print "Print as hash:\n";
foreach my $key (keys (%hash)) {
        print "$key->$hash{$key}\n";
}
print "End\n"; 
  
untie %hash; 
  
    执行结果如下: 
  
Print as hash:
me->Sangyb
me->Sangyb
me->Sangyb
End 
  
    问题出现了……虽然它存储了3个相同的key,但是我明明存储的是不同的value,打出来的却是相同的value。 
    原因是一个叫做联想数组的东西,当你请求同样的key的时候,只会返回第一个value。所以,我们的数据其实已经存储好了,只是在读出来的时候,由于某些原因,出现了这样的问题。 
     
    这个时候,就必须利用api来进行操作了。 
    它提供了一个seq的函数,用来对btree中的对象进行指向: 
  
#!/usr/bin/perl
# test duplicate keys of DB_File
# create by lianming: 2009-08-10
# last modify by lianming: 2009-08-10 
  
use strict;
use warnings;
use DB_File; 
  

my %hash;
my $file_name = "test_btree";
unlink $file_name; 
  
# == enable duplicate records
$DB_BTREE->{'flags'} = R_DUP; 
  
my $db = tie(%hash, 'DB_File', $file_name, O_CREAT|O_WRONLY, 0666, $DB_BTREE)
    || die "Cannot open $file_name: $!\n"; 
  
my ($k, $v, $status); 
  
## == Add some info ==
$hash{'me'} = "Sangyb";
$hash{'me'} = "Lianming";
$hash{'me'} = "Here"; 
  
print "Print as api:\n"; 
  
## == use api: seq ==
$k = $v = 0;
for ($status = $db->seq($k, $v, R_FIRST);
        $status == 0;
        $status = $db->seq($k, $v, R_NEXT)) {
        print "$k->$v\n";

  
print "End\n"; 
  
undef $db;
untie %hash; 
  
    执行结果如下: 
  
Print as api:
me->Sangyb
me->Lianming
me->Here
End 
  
    在读取btree文件的时候,会有一个指针指向一个特定的key/value对,然后读取,写入的操作,就对这个指针指向的pair进行操作。seq函数就是控制这个指针的移动,它有三个参数: 
    第一个,指针指向的pair的key 
    第二个,指针指向的pair的value 
    第三个,flag,调用函数时,指针做的操作: 
        R_CURSOR:当前 
        R_FIRST:最前面的一个pair 
        R_LAST:最后的一个pair 
        R_NEXT:下一个pair 
        R_PREV:前一个pair 
    如果调用失败了,就会返回0。 
  
    针对duplicate key,还有别的几个实用的函数,例如get_dup,del_dup等。 
    get_dup可以获取重复键的一些信息,del_dup可以删除特定的pair。 
  
    当多个进程同时对一个文件进行操作的时候,必然要涉及到一个锁的问题,多个进程如果同时对一个文件进行写,那么肯定会挂掉,我们可以做一些测试。 
1、两个进程同时进行读操作 
  
#!/usr/bin/perl
# test multi read of DB_File
# create by lianming: 2009-08-10
# last modify by lianming: 2009-08-10 
  
use strict;
use warnings;
use DB_File; 
  

my %hash;
my $file_name = "test_btree";
unlink $file_name; 
  
my $db = tie(%hash, 'DB_File', $file_name, O_CREAT|O_WRONLY, 0666, $DB_BTREE)
    || die "Cannot open $file_name: $!\n"; 
  
## == Add some info ==
for (my $i = 0; $i < 20; $i ++) {
        $hash{$i} = $i."test";

  
## == multi read == 
  
my $pid = fork(); 
  
if (!defined($pid)) {
        print "Fork error: $!\n";
        exit 1;

  
if ($pid == 0) {
        ## == child ==
        my $key; 
  
        for (my $i = 0; $i < 1000; $i ++) {
                $key = int(rand()*20);
                print "Child: $key->$hash{$key}\n";
        }
        undef $db;
        untie %hash;
        exit 0;
} else {
        ## == parent ==
        my $key; 
  
        for (my $i = 0; $i < 1000; $i ++) {
                $key = int(rand()*20);
                print "Parent: $key->$hash{$key}\n";
        }

  
undef $db;
untie %hash; 
  
    执行结果是正常的,child和parent会交替出现,打出正确的读取结果。 
  
... 
Parent: 15->15test
Child: 1->1test
Parent: 5->5test
Child: 10->10test
Parent: 3->3test
Child: 5->5test
Parent: 4->4test
Child: 9->9test
Parent: 17->17test
Child: 17->17test
Parent: 9->9test
Child: 7->7test
Parent: 15->15test
Child: 2->2test
Parent: 15->15test 
... 
  
    2、两个进程同时写 
  
#!/usr/bin/perl
# test multi write of DB_File
# create by lianming: 2009-08-10
# last modify by lianming: 2009-08-10 
  
use strict;
use warnings;
use DB_File; 
  

my %hash;
my $file_name = "test_btree";
unlink $file_name; 
  
my $db = tie(%hash, 'DB_File', $file_name, O_CREAT|O_WRONLY, 0666, $DB_BTREE)
    || die "Cannot open $file_name: $!\n"; 
  
## == multi read == 
  
my $pid_1 = fork(); 
  
if (!defined($pid_1)) {
        print "Fork error: $!\n";
        exit 1;

  
if ($pid_1 == 0) {
        ## == child ==
        my $key; 
  
        for (my $i = 0; $i < 1000; $i ++) {
                $key = int(rand()*10);
                $hash{$key} = $key."abc";
        }
        undef $db;
        untie %hash;
        exit 0;

  
my $pid_2 = fork(); 
  
if (!defined($pid_2)) {
        print "Fork error: $!\n";
        exit 1;

  
if ($pid_2 == 0) {
        ## == child ==
        my $key; 
  
        for (my $i = 0; $i < 1000; $i ++) {
                $key = int(rand()*10)+10;
                $hash{$key} = $key."abc";
        }
        undef $db;
        untie %hash;
        exit 0;

  
wait(); 
  
## == parent ==
my $cnt = 0; 
  
foreach my $key (keys(%hash)) {
        print "$key->$hash{$key}\n";
        $cnt ++;
}
print "\ncount is $cnt\n"; 
  
undef $db;
untie %hash; 
  
    执行结果如下: 
  
10->10abc
11->11abc
12->12abc
13->13abc
14->14abc
15->15abc
16->16abc
17->17abc
18->18abc
19->19abc 
  
count is 10 
我们本来是想让他有20个值的,结果只有第二个子进程的修改生效了,第一个子进程的修改并没有生效。 
  
    我们可以得到一个结论,那就是DB_File可以支持多个进程同时读,但是无法同时写。 
    具体原因,我只能猜测一下了,那就是tie,在文件开始的tie,是将文件内容和哈希变量绑定,我们可以认为,其实就是一个指针,指到了文件的开 始位置,读的话,是互不干涉的,所以可以多个进程同时读。但是写的时候,也许写的操作并不是直接写文件,而是修改哈希的值,最后在untie,或者需要的 时候,才会把对哈希的操作刷到文件中(也可以用$db->sync来实现),所以,虽然两个进程都对文件进行了写的操作,但是只有最后untie的 一个才会生效。 
    如果要测试的话,可以在第二个子进程undef之前,sleep几秒钟,就可以看到,生效的就是第一个进程的写入了。 
    当然,这只是最简单的情况,我测试过多个进程同时对它进行大量的写操作,那最后文件就会乱七八糟,甚至有可能没法打开。 
  
    这种情况下,是需要对文件上锁的,在锁住文件之后,才对它进行tie,在解锁之前,要进行untie。有两个给DB_File上锁的包,就拿DB_File::Lock来做例子: 
  
#!/usr/bin/perl
# test lock of DB_File
# create by lianming: 2009-08-10
# last modify by lianming: 2009-08-10 
  
use strict;
use warnings;
use DB_File;
use DB_File::Lock;
use Fcntl qw(:flock O_RDWR O_CREAT); 
  

unlink $file_name; 
  
my $pid_1 = fork(); 
  
if (!defined($pid_1)) {
        print "Fork error: $!\n";
        exit 1;

  
if ($pid_1 == 0) {
        ## == child ==
        my $key; 
  
        for (my $i = 0; $i < 1000; $i ++) {
                $key = int(rand()*12)+122; 
  
                ## == lock and tie == 
                tie(%hash, 'DB_File::Lock', $file_name, O_CREAT|O_WRONLY, 0666, $DB_BTREE, "write") || die "Cannot open $file_name: $!\n";
                $hash{$key} = $key."abc";
                untie %hash;
        }
        exit 0;

  
my $pid_2 = fork(); 
  
if (!defined($pid_2)) {
        print "Fork error: $!\n";
        exit 1;

  
if ($pid_2 == 0) {
        ## == child ==
        my $key; 
  
        for (my $i = 0; $i < 1000; $i ++) {
                $key = int(rand()*10)+10; 
  
                ## == lock and tie ==
                tie(%hash, 'DB_File::Lock', $file_name, O_CREAT|O_WRONLY, 0666, $DB_BTREE, "write") || die "Cannot open $file_name: $!\n";
                $hash{$key} = $key."abc";
                untie %hash;
        }
        exit 0;

  
wait(); 
  
## == parent == 
  
tie(%hash, 'DB_File::Lock', $file_name, O_CREAT|O_RDONLY, 0666, $DB_BTREE, "read") || die "Cannot open $file_name: $!\n"; 
  
my $cnt = 0; 
  
foreach my $key (keys(%hash)) {
        print "$key->$hash{$key}\n";
        $cnt ++;
}
print "\ncount is $cnt\n"; 
  
untie %hash; 
  
    执行结果为: 
  
10->10abc
11->11abc
12->12abc
122->122abc
123->123abc
124->124abc
125->125abc
126->126abc
127->127abc
128->128abc
129->129abc
13->13abc
130->130abc
131->131abc
132->132abc
133->133abc
14->14abc
15->15abc
16->16abc
17->17abc
18->18abc
19->19abc 
  
count is 22 
  
    这就是我们想要的结果。在tie的时候,同时上锁,在untie的时候解锁。和DB_File的tie参数一样,最后加一个read或者write就ok 
    锁有两种,read和write,read锁可以多个进程同时持有,但是write锁只能一个进程持有。 
  
    如果说btree和hash的存储算法,是相当于把哈希变量存在了文件中,那recno就是把数组变量存在了文件中,recno因为用的不多,也没有具体看过。 
    对DB_File的性能做一些测试,具体测试代码和结果就不写了,如果大家有兴趣可以自己去搞一下,只写一下测试的结果。 
    1、测试随机的写性能:分别用两种引擎,单进程对一个文件随机写入100w条数据,测试结果如下: 
        HASH:耗时53s,文件大小为83M 
        BTREE:耗时12s,文件大小为120M 
    可见写的话,btree性能稍好,但是占用空间较大。 
    2、测试随机读的性能:分别用两种引擎,单进程对一个文件随机写入100w条数据,测试结果如下: 
        HASH:耗时28s 
        BTREE:耗时28s 
    读性能两个引擎差不多。 
    3、并发性能: 
    a、用BTREE引擎,开启10个进程,每个进程对一个文件写入1w条数据,写入每条数据之前加锁,写完后解锁。测试结果耗时1000多s,可见它的并发性是何等的差劲…… 
    b、用BTREE引擎,单进程,对一个文件写入10w条数据,写入每条数据前加锁,写完后解锁。测试结果是2分钟。 
  
    可见,单进程的读写,还是很快的,但是涉及到加锁后就很慢很慢,慢的主要是tie和untie的过程,这部分的过程涉及到文件的读写,没有任何并 发的机制,所以只有等每次锁了之后才可以tie和untie,这部分的时间消耗是很可观的。并发的问题,其实有另外的办法可以解决,下一篇文档就讨论一种 并发性很高的文件存储策略。
0 0