Mouse のプロパティの weak_ref
の使いどころで「う…」ってなったのでメモ。
先輩方に良記事を教えてもらいましたが、もともと参照とかそっち系の話にはてんで弱い系プログラマーなので、自分なりにまとめてみてもよいかと思いまして候う。
参照カウンタ
- 変数の値が参照されている箇所を数え上げているもの
- Perl では参照カウンタが 0 になるとその変数が解放される
簡単な例で見てみます。
use strict;
use warnings;
use Devel::Peek;
my $piyo = 'piyo';
Dump $piyo;
my $piyo_ref = \$piyo;
Dump $piyo;
上記のプログラムを実行すると結果は以下のようになり、REFCNT
(参照カウント)が増えているのがわかります。
SV = PV(0x7fd4aa03ab90) at 0x7fd4aa08b408
REFCNT = 1
FLAGS = (PADMY,POK,pPOK)
PV = 0x7fd4a9c21a90 "piyo"\0
CUR = 4
LEN = 16
SV = PV(0x7fd4aa03ab90) at 0x7fd4aa08b408
REFCNT = 2
FLAGS = (PADMY,POK,pPOK)
PV = 0x7fd4a9c21a90 "piyo"\0
CUR = 4
LEN = 16
循環参照
- お互いに参照し合う状態のこと
- Perl は参照カウンタを用いてメモリ管理しているため、循環参照がメモリリークを引き起こす原因となる(プログラム終了時まで参照カウンタが 0 にならなくなっちゃう場合があるので)
例えば Mouse を用いて以下のようなオブジェクトを定義したとします。
package Piyo;
use Mouse;
has poyo => (
is => 'rw',
);
sub set_poyo {
my ($self, $poyo) = @_;
$self->poyo($poyo);
}
__PACKAGE__->meta->make_immutable;
no Mouse;
1;
package Poyo;
use Mouse;
has piyo => (
is => 'rw',
);
sub set_piyo {
my ($self, $piyo) = @_;
$self->piyo($piyo);
}
__PACKAGE__->meta->make_immutable;
no Mouse;
1;
で、Devel::Cycle を用いた以下のようなコードで循環参照になっちゃってるかどうかを調べてみます。
use strict;
use warnings;
use Devel::Cycle;
use Piyo;
use Poyo;
my $piyo = Piyo->new();
my $poyo = Poyo->new();
$poyo->set_piyo($piyo);
$piyo->set_poyo($poyo);
find_cycle($piyo);
結果、以下のように循環参照が検出されます。
Cycle (1):
$Piyo::A->{'poyo'} => \%Poyo::B
$Poyo::B->{'piyo'} => \%Piyo::A
循環参照を解決する
- その 1:Scalar::Util::weaken を使って、参照カウンタを増やさないようにする
package Piyo;
use Mouse;
has poyo => (
is => 'rw',
);
sub set_poyo {
my ($self, $poyo) = @_;
$self->poyo($poyo);
Scalar::Util::weaken($self->poyo);
}
__PACKAGE__->meta->make_immutable;
no Mouse;
1;
- その 2:Moose とか Mouse のプロパティを弱い参照にして、スコープを抜けると参照が破棄されるようにする
package Piyo;
use Mouse;
has poyo => (
is => 'rw',
weak_ref => 1,
);
sub set_poyo {
my ($self, $poyo) = @_;
$self->poyo($poyo);
}
__PACKAGE__->meta->make_immutable;
no Mouse;
1;