複数プロセスでの値のやりとりについて

複数プロセスでの処理を簡易的に行う場合、
私は、forkやParallel::ForkManagerを使いますが、
forkした複数の処理結果を共有する亊ができないため、
これまで、プロセス間通信を行うIPC::ShareableやIPC::Liteを使って、
forkした結果を共有できるようにしていました。

ただ、IPC系モジュールの挙動をあまり理解できていなかったため、
理解を深められたらとこのエントリーを書いてみました。

例ですが、perlモジュールのバージョンをチェックするスクリプトです。
モジュール名をfork関数を使って1プロセスずつ問い合わせ、結果をIPC::Liteモジュールで定義したグローバル変数に追加をしています。

use strict;

use IPC::Lite qw($result);
$result = undef;

my @name_list = qw( DBI CGI LWP::Simple );

foreach ( @name_list ){
    my $pid = fork;
    if ($pid) {
        wait;
    } else {
        $result->{$_}  = &working_process($_);
        exit(0);
    }
}

sub working_process {
    my $module = shift;

    my $required_module = $module;
    $required_module =~ s{::}{/}g;

    eval { require "$required_module.pm"; };
    if ($@) {
        return "not_installed";
    } else {
        no strict 'refs';
        return ${$module."::VERSION"};
    }
}

use Data::Dumper;
print Dumper( $result );

## resultに格納した値は処理の最終的にクリアしておきたいので...
foreach( keys %$result ){
    $result->{$_} = undef;
}

1;

IPCと名の付くモジュールの中で、今回はIPC::Liteの挙動を理解したかったので、モジュールを少し読んでみました。

上記の例では、各プロセスで取得したperlモジュールのバージョン値をプロセス間で共有するために、IPC::Liteではたいへんザックリですが、以下のような流れで処理されていると理解しました。

1. IPC::Liteをuseする部分で、まず今処理しているパッケージ上のグローバル変数 「result」がtie関数でフックされる。
2. フックされた「result」をキーに各プロセスで取得したモジュールのモジュール名やバージョン値などを、SQLiteにinsertする。なお、2回目以降は、「result」という名でSQLiteに登録されているデータを取得し、登録されていないものがあれば、追加でinsertを行う。

IPCを理解するには、まだ程遠い気がしますが、IPC系のモジュールももっと数多く読んで理解を深めていけたらと思います。

勘違いなどありましたら、ご指摘いただければありがたいです。

よろしくお願い致します。

タイ(tie)関数について その2

次はタイ配列について学んでみました。

タイ配列の場合は、以下のメソッドが存在します。

  1. TIEARRAYメソッド コンストラク
  2. DESTROYメソッド ガーベジコレクター
  3. FETCHメソッド tie配列の各要素を参照しようとする場合に必要
  4. STOREメソッド tie配列の各要素にアクセスしようとする場合に必要
  5. FETCHSIZEメソッド tieした配列を参照しようとする場合に必要
  6. PUSHメソッド tieした配列に対してpushした場合に必要
  7. POPメソッド tieした配列に対してpopした場合に必要
  8. SHIFTメソッド tieした配列に対してshiftした場合に必要
  9. UNSHIFTメソッド tieした配列に対してunshiftした場合に必要
  10. SPLICEメソッド tieした配列に対してspliceした場合に必要
  11. CLEARメソッド tieした配列自体を空にする場合などに必要なメソッド
  12. EXTENDメソッド tieした配列を拡張する場合などに必要なメソッド

実際にどのメソッドを使うかは、タイ配列をどのように実装したいかによって異なります。

{
    package TieWeight;

    use strict;
    use warnings;
    use YAML::Syck;

    sub TIEARRAY {
        my ( $class, $yaml ) = @_;
        my $self = {};
        $self->{weight} = __PACKAGE__->_load_yaml($yaml);
        bless $self, $class;
    }

    sub FETCHSIZE {
        my $self = shift;
        return scalar( @{$self->{weight}} );
    }

    sub PUSH {
        my $self = shift;
        my $weight = shift;
        push( @{$self->{weight}}, $weight );

        DumpFile("weight.yaml", $self->{weight});
    }

    sub _load_yaml{
        my $self = shift;
        my $yaml = shift;
        return YAML::Syck::LoadFile( $yaml );
    }
}

{
    package main;
    use strict;
    use warnings;

    tie my @weight_list, 'TieWeight', 'weight.yaml';

    push( @weight_list , '69.5' );
    push( @weight_list , '69.1' );

}

# ※weight.yaml
# コメントアウトをはずして使ってください。
#---
#- 68.0
#- 69.2
#- 69.5
#- 68.5
#- 68.2
#- 68.0
#- 67.5
#- 67.2
#- 66.9
#- 68.2

すごく単純な例として上記のスクリプトを作成してみました。

スクリプトを実行すると、weight.yamlに保存された過去10日間の体重データは、tie関数でロードされたTieWeightクラスによって配列に格納されます。あとは配列にどんどん新規取得した体重データをpushし、weight.yamlにdamp出力しています。

まだ、Tieを理解できているのか不安ですが、
使い込むことで慣れていけたらと思います。

勘違いなどありましたら、ご指摘いただければありがたく思います。
よろしくお願いいたします。

Composite パターン

Composite パターン。

Composite パターンは複数の処理クラスが持つオブジェクトを「複合」し、
複合したオブジェクトを持つクラスが、処理クラスの処理すべてを、一括で行ってくれるものです。

{
    package Car::Factory::Interface;
    use Moose::Role;
    requires qw( made_car examine_car );
    no Moose::Role;
    1;
}

{
    package Car::Factory::PassengerCar;
    use Moose;
    with 'Car::Factory::Interface';

    has 'displacement' => (
        metaclass => 'Number',
        is => 'rw',
        isa => 'Num',
        required => 1,
        provides => {
            mul => 'displacement_mul'
        }
    );

    __PACKAGE__->meta->make_immutable;

    no Moose;
    sub made_car {
        my ( $self, $factor ) = @_;
        $self->displacement_mul( $factor );
    }

    sub examine_car {
        my $self = shift;
        print $self->displacement, "ccの乗用車が完成。\n";
    }

    1;
}

{
    package  Car::Factory::Truck;
    use Moose;
    with 'Car::Factory::Interface';

    has 'weight' => (
        metaclass => 'Number',
        is => 'rw',
        isa => 'Num',
        required => 1,
        provides => {
            mul => 'weight_mul'
        }
    );

    __PACKAGE__->meta->make_immutable;

    no Moose;
    sub made_car {
        my ( $self, $factor ) = @_;
        $self->weight_mul( $factor );
    }

    sub examine_car {
        my $self = shift;
        print $self->weight, "tのトラックが完成。\n";
    }
    1;
}

{
    package Car::Factory;
    use Moose;

    use MooseX::AttributeHelpers;
    with 'Car::Factory::Interface';

    has 'car_means' => (
        metaclass => 'Collection::Array',
        is => 'rw',
        isa => 'ArrayRef',
        auto_deref => 1,
        default => sub { [] },
        provides => {
            push => 'car_add',
        }
    );

    __PACKAGE__->meta->make_immutable;
    no Moose;
    sub made_car {
        my ( $self, $factor ) = @_;
        my $list = $self->car_means;
        foreach my $car_means ( @$list ){
             $car_means->made_car( $factor );
        }
    }

    sub examine_car {
        my $self = shift;
        my $list = $self->car_means;
        foreach my $car_means ( @$list ){
             $car_means->examine_car();
        }
    }
    1;
}

{
    package main;
    my $car1 = Car::Factory::PassengerCar->new( displacement => 550 );
    my $car2 = Car::Factory::PassengerCar->new( displacement => 1000 );
    my $car3 = Car::Factory::PassengerCar->new( displacement => 3000 );

    my $car4 = Car::Factory::Truck->new( weight => 3 );
    my $car5 = Car::Factory::Truck->new( weight => 2 );
    my $car6 = Car::Factory::Truck->new( weight => 5 );

    my $factory = Car::Factory->new;
    $factory->car_add( $car1 );
    $factory->car_add( $car2 );
    $factory->car_add( $car3 );
    $factory->car_add( $car4 );
    $factory->car_add( $car5 );
    $factory->car_add( $car6 );

    $factory->made_car(1);
    $factory->examine_car();
    1;
}

またまた拙い例ですが、上記のコードはある自動車工場をイメージしました。

工場では、乗用車を製造するCar::Factory::PassengerCarクラスと、
トラックを製造するCar::Factory::Truckクラスがあり、
担当のクラスがそれぞれ自動車を製造しています。

ある日工場では、効率性を上げるためにCar::Factoryクラスが追加されました。
Car::Factoryクラスは、乗用車クラスとトラッククラスを一括で処理してくれるクラスであるため、
生産性は向上します。

勘違いとかありましたら、ご指摘いただければありがたく思います。
よろしくお願いいたします。

タイ(tie)関数について

タイ関数(tie)について、あまり理解できなかったので、
勉強してみたいと思います。

タイ関数の場合、タイスカラー、タイ配列、タイハッシュなど、
データの形式によってタイ関数も異なりますが、
まずはタイスカラーのケースから考えてみました。

タイスカラーの場合は、以下のメソッドが存在します。

  1. コンストラクタ TIESCALARメソッド
  2. ガーベジコレクター DESTROYメソッド
  3. 値を取得する FETCHメソッド
  4. 値を設定する STOREメソッド

DESTROYメソッドはなくても、エラーにはなりませんが、
他のメソッドは存在しない場合、エラーとなります。

例として、以下のようなケースを考えてみました。
memcachedからデータ取得をするTie::Memcachedクラス
をフックしつつ、任意のキーワードを投げて、memcachedにデータが存在すれば、
結果を受け取れるというスクリプトです。

{
    package Tie::Memcached;

    sub TIESCALAR {
        my $class = shift;
        my $memd  = shift;
        my $self = bless { memd => $memd }, $class;
        return $self;
    }

    sub FETCH {
        my $self = shift;
        return $self->{result}
    }

    sub STORE {
        my $self = shift;
        my $word = shift;
        $self->{result} = $self->{memd}->get($word);
    }
    1;
}

{
    package main;
    use Cache::Memcached;
    use Data::Dumper;

    my $memd = new Cache::Memcached { 'servers' => ["127.0.0.1:11211"] };
    tie my $scalar, 'Tie::Memcached', $memd;

    ## キャッシュに問い合わせするキーワード
    my @word_list = qw( word01 word02 word03 );
    foreach (@word_list) {
        my $result = $scalar = $_;
        print Dumper($result);
    }
    1;
}

Chain of Responsibility パターン

次は、Chain of Responsibility パターン。

意味は以下のとおり理解しました。

それぞれ異なる処理機能をもった実装クラスが複数存在するとし、
それらを管理する親クラスが存在します。

親クラスに対してある処理依頼をした時、
親クラスは実装クラスに対して順番に依頼を行い、
処理を担当できる実装クラスが存在すれば処理を実施します。

{
    package DeliveryImpl;
    use Moose::Role;
    requires 'delivery';
    no Moose::Role;
    1;
}

{
    package Delivery;
    use Moose;
    use MooseX::AttributeHelpers;

    has 'deliveries' => (
        metaclass => 'Collection::Array',
        is        => 'ro',
        isa       => 'ArrayRef[DeliveryImpl]',
        default   => sub { [] },
        provides  => { 'push' => 'add_request', }
    );

    __PACKAGE__->meta->make_immutable;
    no Moose;

    sub delivery {
        my ( $self, $type, $comment ) = @_;
        my $handled;

        my $list = $self->deliveries;
        foreach my $handler (@$list) {
             $handled = $handler->delivery( $type, $comment );
        }
    }
    1;
}

{
    package Delivery::Susi;
    use Moose;
    with 'DeliveryImpl';
    __PACKAGE__->meta->make_immutable;
    no Moose;

    sub delivery {
        my ( $self, $type ) = @_;
        if ( $type eq 'Susi' ) {
            print $type , "を作る\nヘイ!お待ち!\n";
        }
    }
    1;
}

{
    package Delivery::Udon;
    use Moose;
    with 'DeliveryImpl';
    __PACKAGE__->meta->make_immutable;
    no Moose;

    sub delivery {
        my ( $self, $type ) = @_;
        if ( $type eq 'Udon' ) {
            print $type , "を作る\nヘイ!お待ち!\n";
        }
    }
    1;
}

{
    package main;
    my $wk = Delivery->new;
    $wk->add_request( Delivery::Susi->new() );
    $wk->add_request( Delivery::Udon->new() );
    $wk->delivery( 'Susi' );
    $wk->delivery( 'Udon' );
    1;
}

拙い例ですが、上記のコードはある出前屋さんをイメージしました。

出前には、すしを配達するDelivery::Susiクラスと、
うどんを配達するDelivery::Udonクラスがありました。
そして上記2クラスを管理するDeliveryクラスがありました。
Delivery::SusiクラスとDelivery::Udonクラスには、
deliveryメソッドが存在し、deliveryを定義づけしているのは、
DeliveryImplクラスです。
メソッドを新たに追加する場合は、まずDeliveryImplクラスで定義を行います。

勘違いとかありましたら、ご指摘いただければありがたく思います。
よろしくお願いいたします.

Mooseデザインパターンもそうですが、とても高度で、他にまず理解すべきことは山ほどあるのですが、せっかく学びはじめたので、しっかり理解できればと思います。

Bridgeパターン

Mooseを用いたデザインパターンの勉強をしています。
牧大輔さんの「モダンPerl入門」を参考に、考え方をすこしずつ理解していきたいです。
現場でMooseを直接使ってはいないのですが、
いつか効率的なコードを書くのに生かせることができればと思います。


あまり目新しいことは書けないのですが、
自分の理解した内容を確認する意味でエントリーしました。


最初はBridgeパターン。
機能を示すクラスと、実際に実装を行うクラスを分割し、
Moose::Roleを持った抽象クラスが、機能クラスと実装クラスの
架け橋的な役割をしています。

use strict;
use warnings;

package CalculateImpl;
use Moose::Role;
requires qw( calculate_num );
no Moose::Role;
1;


# 足し算クラス
package Calculate::Addition;
use Moose;
with 'CalculateImpl';

__PACKAGE__->meta->make_immutable;

no Moose;
sub calculate_num {
my ( $self, $num ) = @_;
return $num + $num;
}
1;


# 掛け算クラス
package Calculate::Multiplication;
use Moose;
with 'CalculateImpl';

__PACKAGE__->meta->make_immutable;

no Moose;
sub calculate_num {
my ( $self, $num ) = @_;
return $num * $num;
}
1;


package Calculate;
use Moose;

has 'handle' => (
is => 'rw',
does => 'CalculateImpl',
required => 1,
handles => [ qw( calculate_num ) ]
);

__PACKAGE__->meta->make_immutable;

use Moose;
1;

package main;
use strict;
my $add = Calculate->new( handle => Calculate::Addition->new());
my $multi = Calculate->new( handle => Calculate::Multiplication->new());

$add->calculate_num( 50 );
$multi->calculate_num( 50 );
1;



計算という機能を意味するCalculateクラスのほかに、
実際に計算処理を実装する、
足し算クラスのCalculate::Additionクラスと
掛け算クラスのCalculate::Multiplicationクラスが存在します。
そして、機能クラスと実装クラスをつなぐ役割として、
CalculateImplクラスが存在します。


このような構成をすることで、何か変更が発生した場合は、
実装クラスを直接修正すればよいので、
機能クラスであるCalculateクラスを触る必要はありません。


勘違いをしてしまっている場合はご指摘いただければ助かります。

さくらレンタルサーバーでGitHubを導入する際の覚書。

以前、GitHubをさくらのレンタルサーバーで実行する方法を試してみたのですが、
その際の覚書です。

GitHubを導入したいと考えた理由として、
さくらのレンタルサーバーを
PerlCatalystでWebアプリケーションを作るための環境として
レンタルしていたのですが、
gitHubが使えると、手持ちのノートパソコンでも
ローカルでコーディングができるので
とても便利だろうなぁと感じていました。

ほとんど以下の参考ページを模倣しているだけなのですが、
実際に動作させながら確認しました。

参考ページ
http://d.hatena.ne.jp/keisukefukuda/20080520/p1

使用環境としては以下のとおりです。
公開サーバー:さくらのレンタルサーバー(スタンダードプラン)
クライアント:Ubuntu 8.04

1. 公開サーバーにGitをインストール
※最新版のアーカイブを以下のサイトからwgetする。
http://kernel.org/pub/software/scm/git/

wget http://kernel.org/pub/software/scm/git/git-1.6.3.3.tar.gz
tar zxvf git-1.6.3.3.tar.gz
cd git-1.6.3.3
./configure --prefix=$HOME/local
gmake
gmake install


2. 公開サーバー側作業
cd $HOME
mkdir repos.git
cd repos.git
echo "This is my git repository" > README
git init-db
git add README
/home/seeherher/local/bin/git commit -m "first commit"

3. クライアント側作業
git clone ssh://youraccount@your.sakura.domain/home/youraccount/repos.git repos
cd repos
echo 'Second Commit!' >> README
git add README
git commit -m "second commit"
git push origin


4. 公開レポジトリ側作業
git pull repos.git
git reset --hard

少し迷ったのは、git resetコマンドを実行しないと
公開サーバー側にクライアント側での変更内容が反映されないことでした。

GitHubは見慣れないコマンドがほとんどですが、少しずつ慣れてゆきたいです。