2011-11-20 15 views
16

Gwinty Perla nie obsługują udostępniania uchwytów plików. Wszystkie elementy udostępnionej struktury danych muszą być udostępniane. Stanowi to problem, jeśli trzeba udostępnić obiekt zawierający uchwyt pliku.Jak udostępnić obiekt zawierający uchwyt pliku?

{ 
    package Foo; 
    use Mouse; 

    has fh => 
     is  => 'rw', 
     default => sub { \*STDOUT }; 
} 

use threads; 
use threads::shared; 
my $obj = Foo->new; 
$obj = shared_clone($obj);   # error: "Unsupported ref type: GLOB" 
print {$obj->fh} "Hello, world!\n"; 

To naprawdę nie ma znaczenia, czy uchwyt pliku jest "udostępniony", czy nie, służy tylko do wydruków. Być może istnieje sztuczka, w której uchwyt pliku jest przechowywany poza udostępnionym obiektem?

Ten obiekt jest aktualnie zawarty w innym obiekcie udostępnionym, który znajduje się w innym obiekcie i tak dalej. Wielka ironia polega na tym, że przedmiotowe obiekty nigdy nie używają samych wątków, ale muszą pozostać skoordynowane w całym procesie, jeśli użytkownik używa wątków.

Rzeczywisty kod, o którym mowa: can be seen here: Te obiekty służą do konfiguracji miejsca, w którym znajduje się sformatowane wyjście. Obiekt jest konieczny, ponieważ output does not always go to a filehandle.

+1

+1 za to, że zmuszam mnie do szczegółowego zapoznania się z działaniem wątków w Perlu. -1 za kradzież 5 godzin snu :) – DVK

Odpowiedz

6

Nie mam obecnie dostępu do gwintowanego Perla, więc nie mogę zagwarantować, że to zadziała.

Ale nieco uproszczone podejście byłoby użyć poziom abstrakcji i przechowywać klucz/indeks do globalnej filehandle hash/tablicy do obiektu, coś podobnego do poniższego:

my @filehandles =(); # Stores all the filehandles   ### CHANGED 

my $stdout; # Store the index into @filehandles, NOT filehandle. 
      # Should really be renamed "$stdout_id" instead. 

sub stdout { 
    my $self = shift; 

    return $stdout if defined $stdout; 

    $stdout = scalar(@filehandles);       ### CHANGED 
    my $stdout_fh = $self->dup_filehandle(\*STDOUT);  ### CHANGED 
    push @filehandles, $stdout_fh;       ### CHANGED 

    $self->autoflush($stdout_fh);       ### CHANGED 
    $self->autoflush(\*STDOUT); 

    return $stdout; 
} 

sub safe_print { 
    my $self = shift; 
    my $fh_id = shift;          ### CHANGED 
    my $fh = $filehandles[$fh_id];       ### CHANGED 

    local($\, $,) = (undef, ''); 
    print $fh @_; 
} 

mam silne odczucie, że musisz w jakiś sposób również zabezpieczać listę identyfikatorów, więc być może potrzebny będzie współdzielony licznik indeksu zamiast $stdout = scalar(@filehandles);

+0

Widzę, dokąd zmierzasz. Myślałem o czymś takim. Jednym z problemów jest to, że jeśli nowy uchwyt pliku zostanie otwarty w wątku, nie będzie widoczny przez żaden z pozostałych wątków lub rodzica. Cieszę się z mówienia użytkownikom "nie rób tego". Nie sądzę, aby zmiana wyników w wątku działała z bieżącym testem :: Builder i nikt nie narzekał. – Schwern

+0

@Schwern - zobacz moją drugą odpowiedź. Nie wiem, jak dobrze to działa – DVK

+0

Myślę, że będę musiał użyć jakiejś wariacji na ten temat, ale mogę ją zedrzeć w podklasie specyficznej dla wątku, więc normalne użycie nie jest wynikiem tej bleterowatości. – Schwern

5

Jako alternatywę dla mojej innej odpowiedzi z globalną tablicą, oto inne podejście z Perlmonks:

http://perlmonks.org/?node_id=395513

Działa przez przechowywanie fileno (deskryptora pliku) uchwytu pliku. Oto jego kod próbki w oparciu o to, co BrowserUk pisał:

my $stdout; # Store the fileno, NOT filehandle. 
      # Should really be renamed "$stdout_fileno" instead. 

sub stdout { 
    my $self = shift; 

    return $stdout if defined $stdout; 

    my $stdout_fh = $self->dup_filehandle(\*STDOUT);  ### CHANGED 
    $stdout = fileno $stdout_fh;       ### CHANGED 

    $self->autoflush($stdout_fh);       ### CHANGED 
    $self->autoflush(\*STDOUT); 

    return $stdout; 
} 

sub safe_print { 
    my $self = shift; 
    my $fh_id = shift;          ### CHANGED 
    open(my $fh, ">>&=$fh_id")        ### CHANGED 
     || die "Error opening filehandle: $fh_id: $!\n";  ### CHANGED 

    local($\, $,) = (undef, ''); 
    print $fh @_; 
} 

zastrzeżenie - począwszy od roku 2004, miało to błąd, który nie mógł odczytaćze wspólnego uchwytu pliku z> 1 wątku. Zgaduję, że pisanie jest w porządku. Więcej specyfika jak to zrobić zsynchronizowane pisze na wspólną uchwytu pliku (z tego samego Monk): http://www.perlmonks.org/?node_id=807540

+0

Dobra próba, ale przedmiotowy uchwyt może nie mieć fileno. To może być związane, fajka, skalarna ref ... – Schwern

+0

@Schwern - Byłem pod wrażeniem, że fajka ma deskryptor. Jeśli coś jest związane, masz podwójną śrubę - AFAIK, nie możesz użyć powiązanych danych z wątkami :: udostępnione, ponieważ dzielone są same w sobie - musisz użyć wątku :: Tie. – DVK

3

To właśnie przyszło mi do głowy tam dwa możliwe rozwiązania:

  1. umieścić uchwytu pliku poza obiektem Streamer.
  2. Umieść obiekt Streamer poza formaterem.

@ sugestie DVK są wszystko o zrobieniu 1.

Ale 2 jest w pewnym sensie prostsza niż 1. Zamiast trzymania samego obiektu chorągiew, formater może posiadać identyfikator do obiektu Streamer. Jeśli Streamer jest zaimplementowany od wewnątrz, dzieje się to naturalnie!

Niestety, adresy referencyjne zmieniają się między wątkami, nawet udostępnionymi. Można to rozwiązać za pomocą Hash::Util::FieldHash, ale to jest 5.10 i muszę wesprzeć 5.8. Możliwe, że coś można połączyć za pomocą CLONE.

+1

Obiekt :: InsideOut jest threadafe na 5.8.1+ (http://search.cpan.org/~jdhedden/Object-InsideOut-3.84/lib/Object/InsideOut.pod#THREAD_SUPPORT) - to może ci pomóc. CLONE wygląda na zalecane podejście do tego (https://www.socialtext.net/perl5/inside_out_object) – DVK

+2

@DVK Dzięki, to dobra informacja. Nie mogłem głowic ani ogonów tego, co robi Object :: InsideOut, ale znalazłem ten artykuł Davida Goldena, który w końcu miał zdrowy przykład "CLONE". http://perlmonks.org/index.pl?node_id=483162 – Schwern

1

Oto co likwidacji z ...

package ThreadSafeFilehandle; 

use Mouse; 
use Mouse::Util::TypeConstraints; 

my %Filehandle_Storage; # unshared storage of filehandles 
my $Storage_Counter = 1; # a counter to use as a key 

# This "type" exists to intercept incoming filehandles. 
# The filehandle goes into %Filehandle_Storage and the 
# object gets the key. 
subtype 'FilehandleKey' => 
    as 'Int'; 
coerce 'FilehandleKey' => 
    from 'Defined', 
    via { 
     my $key = $Storage_Counter++; 
     $Filehandle_Storage{$key} = $_; 
     return $key; 
    }; 

has thread_safe_fh => 
    is   => 'rw', 
    isa   => 'FilehandleKey', 
    coerce  => 1, 
; 

# This converts the stored key back into a filehandle upon getting. 
around thread_safe_fh => sub { 
    my $orig = shift; 
    my $self = shift; 

    if(@_) {     # setting 
     return $self->$orig(@_); 
    } 
    else {      # getting 
     my $key = $self->$orig; 
     return $Filehandle_Storage{$key}; 
    } 
}; 

1; 

Korzystanie typu przymus zapewnia, że ​​tłumaczenie z uchwytu pliku do klucza zdarza nawet w konstruktorze obiektu.

To działa, ale ma wady:

Każdy obiekt przechowuje swoją uchwytu pliku nadmiarowo. Jeśli wszystkie obiekty przechowują ten sam uchwyt pliku, prawdopodobnie po prostu przechowują go jeden raz. Sztuką byłoby, jak zidentyfikować ten sam uchwyt pliku. fileno lub refaddr są opcjami.

Uchwyt pliku nie jest usuwany z pliku% Filehandle_Storage po usunięciu obiektu. Pierwotnie wprowadziłem do tego metodę DESTROY, ale ponieważ idiomem do klonowania obiektów jest $clone = shared_clone($obj) $ uchwyt pliku klonu jest niszczony, gdy $ obj wychodzi poza zakres.

Zmiany występujące u dzieci nie są udostępniane.

Są to wszystkie dopuszczalne do moich celów, które będą tworzyć tylko garść tych obiektów na proces.

Powiązane problemy