--- Coro/Coro/Semaphore.pm 2001/07/19 02:45:09 1.7 +++ Coro/Coro/Semaphore.pm 2005/12/12 17:49:07 1.48 @@ -16,20 +16,35 @@ =head1 DESCRIPTION +This module implements counting semaphores. You can initialize a mutex +with any level of parallel users, that is, you can intialize a sempahore +that can be Ced more than once until it blocks. There is no owner +associated with semaphores, so one coroutine can C it while another +can C it. + +Counting semaphores are typically used to coordinate access to +resources, with the semaphore count initialized to the number of free +resources. Coroutines then increment the count when resources are added +and decrement the count when resources are removed. + =over 4 =cut package Coro::Semaphore; +BEGIN { eval { require warnings } && warnings->unimport ("uninitialized") } + use Coro (); -$VERSION = 0.08; +$VERSION = 1.51; -=item new [inital count, default zero] +=item new [inital count] Creates a new sempahore object with the given initial lock count. The -default lock count is 1, which means it is unlocked by default. +default lock count is 1, which means it is unlocked by default. Zero (or +negative values) are also allowed, in which case the semaphore is locked +by default. =cut @@ -42,15 +57,42 @@ Decrement the counter, therefore "locking" the semaphore. This method waits until the semaphore is available if the counter is zero. +=item $status = $sem->timed_down($timeout) + +Like C, but returns false if semaphore couldn't be acquired within +$timeout seconds, otherwise true. + =cut sub down { - my $self = shift; - while ($self->[0] <= 0) { - push @{$self->[1]}, $Coro::current; + while ($_[0][0] <= 0) { + push @{$_[0][1]}, $Coro::current; + Coro::schedule; + } + --$_[0][0]; +} + +sub timed_down { + require Coro::Timer; + my $timeout = Coro::Timer::timeout($_[1]); + + while ($_[0][0] <= 0) { + push @{$_[0][1]}, $Coro::current; Coro::schedule; + if ($timeout) { + # ugly as hell. slow, too, btw! + for (0..$#{$_[0][1]}) { + if ($_[0][1][$_] == $Coro::current) { + splice @{$_[0][1]}, $_, 1; + return; + } + } + die; + } } - --$self->[0]; + + --$_[0][0]; + return 1; } =item $sem->up @@ -60,9 +102,8 @@ =cut sub up { - my $self = shift; - if (++$self->[0] > 0) { - (shift @{$self->[1]})->ready if @{$self->[1]}; + if (++$_[0][0] > 0) { + (shift @{$_[0][1]})->ready if @{$_[0][1]}; } } @@ -74,23 +115,62 @@ =cut sub try { - my $self = shift; - if ($self->[0] > 0) { - --$self->[0]; + if ($_[0][0] > 0) { + --$_[0][0]; return 1; } else { return 0; } } +=item $sem->waiters + +In scalar context, returns the number of coroutines waiting for this +semaphore. + +=cut + +sub waiters { + @{$_[0][1]}; +} + +=item $guard = $sem->guard + +This method calls C and then creates a guard object. When the guard +object is destroyed it automatically calls C. + +=item $guard = $sem->timed_guard($timeout) + +Like C, but returns undef if semaphore couldn't be acquired within +$timeout seconds, otherwise the guard object. + +=cut + +sub guard { + &down; + # double indirection because bless works on the referenced + # object, not (only) on the reference itself. + bless \\$_[0], Coro::Semaphore::guard::; +} + +sub timed_guard { + &timed_down + ? bless \\$_[0], Coro::Semaphore::guard:: + : (); +} + +sub Coro::Semaphore::guard::DESTROY { + &up(${${$_[0]}}); +} + 1; =back =head1 AUTHOR - Marc Lehmann - http://www.goof.com/pcg/marc/ + Marc Lehmann + http://home.schmorp.de/ =cut