1package Thread::Semaphore;
2
3use strict;
4use warnings;
5
6our $VERSION = '2.13';
7$VERSION = eval $VERSION;
8
9use threads::shared;
10use Scalar::Util 1.10 qw(looks_like_number);
11
12# Predeclarations for internal functions
13my ($validate_arg);
14
15# Create a new semaphore optionally with specified count (count defaults to 1)
16sub new {
17    my $class = shift;
18
19    my $val :shared = 1;
20    if (@_) {
21        $val = shift;
22        if (! defined($val) ||
23            ! looks_like_number($val) ||
24            (int($val) != $val))
25        {
26            require Carp;
27            $val = 'undef' if (! defined($val));
28            Carp::croak("Semaphore initializer is not an integer: $val");
29        }
30    }
31
32    return bless(\$val, $class);
33}
34
35# Decrement a semaphore's count (decrement amount defaults to 1)
36sub down {
37    my $sema = shift;
38    my $dec = @_ ? $validate_arg->(shift) : 1;
39
40    lock($$sema);
41    cond_wait($$sema) until ($$sema >= $dec);
42    $$sema -= $dec;
43}
44
45# Decrement a semaphore's count only if count >= decrement value
46#  (decrement amount defaults to 1)
47sub down_nb {
48    my $sema = shift;
49    my $dec = @_ ? $validate_arg->(shift) : 1;
50
51    lock($$sema);
52    my $ok = ($$sema >= $dec);
53    $$sema -= $dec if $ok;
54    return $ok;
55}
56
57# Decrement a semaphore's count even if the count goes below 0
58#  (decrement amount defaults to 1)
59sub down_force {
60    my $sema = shift;
61    my $dec = @_ ? $validate_arg->(shift) : 1;
62
63    lock($$sema);
64    $$sema -= $dec;
65}
66
67# Decrement a semaphore's count with timeout
68#  (timeout in seconds; decrement amount defaults to 1)
69sub down_timed {
70    my $sema = shift;
71    my $timeout = $validate_arg->(shift);
72    my $dec = @_ ? $validate_arg->(shift) : 1;
73
74    lock($$sema);
75    my $abs = time() + $timeout;
76    until ($$sema >= $dec) {
77        return if !cond_timedwait($$sema, $abs);
78    }
79    $$sema -= $dec;
80    return 1;
81}
82
83# Increment a semaphore's count (increment amount defaults to 1)
84sub up {
85    my $sema = shift;
86    my $inc = @_ ? $validate_arg->(shift) : 1;
87
88    lock($$sema);
89    ($$sema += $inc) > 0 and cond_broadcast($$sema);
90}
91
92### Internal Functions ###
93
94# Validate method argument
95$validate_arg = sub {
96    my $arg = shift;
97
98    if (! defined($arg) ||
99        ! looks_like_number($arg) ||
100        (int($arg) != $arg) ||
101        ($arg < 1))
102    {
103        require Carp;
104        my ($method) = (caller(1))[3];
105        $method =~ s/Thread::Semaphore:://;
106        $arg = 'undef' if (! defined($arg));
107        Carp::croak("Argument to semaphore method '$method' is not a positive integer: $arg");
108    }
109
110    return $arg;
111};
112
1131;
114
115=head1 NAME
116
117Thread::Semaphore - Thread-safe semaphores
118
119=head1 VERSION
120
121This document describes Thread::Semaphore version 2.13
122
123=head1 SYNOPSIS
124
125    use Thread::Semaphore;
126    my $s = Thread::Semaphore->new();
127    $s->down();   # Also known as the semaphore P operation.
128    # The guarded section is here
129    $s->up();     # Also known as the semaphore V operation.
130
131    # Decrement the semaphore only if it would immediately succeed.
132    if ($s->down_nb()) {
133        # The guarded section is here
134        $s->up();
135    }
136
137    # Forcefully decrement the semaphore even if its count goes below 0.
138    $s->down_force();
139
140    # The default value for semaphore operations is 1
141    my $s = Thread::Semaphore->new($initial_value);
142    $s->down($down_value);
143    $s->up($up_value);
144    if ($s->down_nb($down_value)) {
145        ...
146        $s->up($up_value);
147    }
148    $s->down_force($down_value);
149
150=head1 DESCRIPTION
151
152Semaphores provide a mechanism to regulate access to resources.  Unlike
153locks, semaphores aren't tied to particular scalars, and so may be used to
154control access to anything you care to use them for.
155
156Semaphores don't limit their values to zero and one, so they can be used to
157control access to some resource that there may be more than one of (e.g.,
158filehandles).  Increment and decrement amounts aren't fixed at one either,
159so threads can reserve or return multiple resources at once.
160
161=head1 METHODS
162
163=over 8
164
165=item ->new()
166
167=item ->new(NUMBER)
168
169C<new> creates a new semaphore, and initializes its count to the specified
170number (which must be an integer).  If no number is specified, the
171semaphore's count defaults to 1.
172
173=item ->down()
174
175=item ->down(NUMBER)
176
177The C<down> method decreases the semaphore's count by the specified number
178(which must be an integer >= 1), or by one if no number is specified.
179
180If the semaphore's count would drop below zero, this method will block
181until such time as the semaphore's count is greater than or equal to the
182amount you're C<down>ing the semaphore's count by.
183
184This is the semaphore "P operation" (the name derives from the Dutch
185word "pak", which means "capture" -- the semaphore operations were
186named by the late Dijkstra, who was Dutch).
187
188=item ->down_nb()
189
190=item ->down_nb(NUMBER)
191
192The C<down_nb> method attempts to decrease the semaphore's count by the
193specified number (which must be an integer >= 1), or by one if no number
194is specified.
195
196If the semaphore's count would drop below zero, this method will return
197I<false>, and the semaphore's count remains unchanged.  Otherwise, the
198semaphore's count is decremented and this method returns I<true>.
199
200=item ->down_force()
201
202=item ->down_force(NUMBER)
203
204The C<down_force> method decreases the semaphore's count by the specified
205number (which must be an integer >= 1), or by one if no number is specified.
206This method does not block, and may cause the semaphore's count to drop
207below zero.
208
209=item ->down_timed(TIMEOUT)
210
211=item ->down_timed(TIMEOUT, NUMBER)
212
213The C<down_timed> method attempts to decrease the semaphore's count by 1
214or by the specified number within the specified timeout period given in
215seconds (which must be an integer >= 0).
216
217If the semaphore's count would drop below zero, this method will block
218until either the semaphore's count is greater than or equal to the
219amount you're C<down>ing the semaphore's count by, or until the timeout is
220reached.
221
222If the timeout is reached, this method will return I<false>, and the
223semaphore's count remains unchanged.  Otherwise, the semaphore's count is
224decremented and this method returns I<true>.
225
226=item ->up()
227
228=item ->up(NUMBER)
229
230The C<up> method increases the semaphore's count by the number specified
231(which must be an integer >= 1), or by one if no number is specified.
232
233This will unblock any thread that is blocked trying to C<down> the
234semaphore if the C<up> raises the semaphore's count above the amount that
235the C<down> is trying to decrement it by.  For example, if three threads
236are blocked trying to C<down> a semaphore by one, and another thread C<up>s
237the semaphore by two, then two of the blocked threads (which two is
238indeterminate) will become unblocked.
239
240This is the semaphore "V operation" (the name derives from the Dutch
241word "vrij", which means "release").
242
243=back
244
245=head1 NOTES
246
247Semaphores created by L<Thread::Semaphore> can be used in both threaded and
248non-threaded applications.  This allows you to write modules and packages
249that potentially make use of semaphores, and that will function in either
250environment.
251
252=head1 SEE ALSO
253
254Thread::Semaphore on MetaCPAN:
255L<https://metacpan.org/release/Thread-Semaphore>
256
257Code repository for CPAN distribution:
258L<https://github.com/Dual-Life/Thread-Semaphore>
259
260L<threads>, L<threads::shared>
261
262Sample code in the I<examples> directory of this distribution on CPAN.
263
264=head1 MAINTAINER
265
266Jerry D. Hedden, S<E<lt>jdhedden AT cpan DOT orgE<gt>>
267
268=head1 LICENSE
269
270This program is free software; you can redistribute it and/or modify it under
271the same terms as Perl itself.
272
273=cut
274