NAME
RPi::WiringPi::WORKERS - concurrency & background-worker examples for RPi::WiringPi
DESCRIPTION
Worked, runnable examples for running background work concurrently with your main program using the object-oriented RPi::WiringPi. The $pi->worker method described here is a thin proxy onto WiringPi::API::worker() (shipped in WiringPi::API 3.18 and verified on Pi 5 hardware); the snippets run as written.
$pi->worker is fork-based by default and needs no use threads and no threaded Perl - an ithread mechanism is a documented opt-in only. For reacting to GPIO edges in the background, see $pi->background_interrupts and RPi::WiringPi::INTERRUPTS.
This is the OO companion to WiringPi::API::WORKERS; that one covers the low-level functional API, this one the $pi object. See the worker(\&body, \%opts) entry in RPi::WiringPi for the per-method reference.
ABOUT THESE EXAMPLES
This document covers running work concurrently with the main program - distinct from reacting to interrupts.
Prefer
$pi->worker. It hides the spawn mechanism, the loop and the lifecycle: your body carries nofork, nouse threads, nodetach, nowhile (1)and no cleanup. It is the general-purpose sibling of$pi->background_interrupts.The object owns the lifecycle. Every handle returned by
$pi->workeris tracked on the object;$pi->cleanup(and thereforeDESTROY) stops them all. You can$w->stopa worker yourself, but you never have to.No threads required.
$pi->workerforks by default and works on any Perl, threaded or not. The ithread mechanism (scenario 6) is an opt-in for users who specifically want shared-memory ergonomics on a threaded Perl.Pin numbering follows the object's scheme (BCM/GPIO by default). Configure pins through the object -
my $pin = $pi->pin($n); $pin->mode(OUTPUT)- once, in the parent, before starting a worker.Sections 7-8 ("UNDER THE HOOD") show the raw
fork/threads->createplumbing that$pi->workerpackages up - read them to understand what happens beneath the method, but most programs only need$pi->worker.
DECISION GUIDE
None of these need use threads except scenario 6. To hide the most plumbing, use $pi->worker (scenarios 1-5).
fork vs thread in one line: the default fork worker is crash-isolated and works on any Perl but can't touch main's variables (hand data back with results/shared); a mechanism => 'thread' worker shares memory directly but needs a threaded Perl and pi_lock discipline. No shared-memory need? Use the default fork.
Run a background task and forget it (its own GPIO) - scenario 1.
Sample periodically; main reads the latest value - scenario 2 (
interval/shared).Stream every value the worker produces back to main - scenario 3 (
results).Do one background job once, then exit - scenario 4 (
once).Several independent workers, each on its own pin - scenario 5.
Share memory directly between main and the worker - scenario 6 (
mechanism => 'thread').React to a pin edge in the background -
$pi->background_interrupts.Understand/hand-roll the raw mechanism - scenarios 7, 8.
BACKGROUND WORKERS ($pi->worker)
1. Heartbeat LED - a worker on its own pin
Why/when: Run a self-contained background task on its own GPIO while main does its own work; the simplest possible case.
Real-world: A status heartbeat LED blinking on its own cadence while the main program does its real work.
Main & background: The method owns the loop and the lifecycle. You write only the body; $pi->worker repeats it until you stop - or until $pi->cleanup reaps it for you.
use strict;
use warnings;
use RPi::WiringPi;
use RPi::Const qw(:all);
my $pi = RPi::WiringPi->new;
my $pin = $pi->pin(2);
$pin->mode(OUTPUT); # once, in main
my $w = $pi->worker(sub {
$pin->write(HIGH); sleep 1;
$pin->write(LOW); sleep 1;
});
# ... main does its own work ...
$pi->cleanup; # stops $w (and every other worker) for you
No use threads, no fork, no detach, no while (1), no waitpid. You could also $w->stop the heartbeat by hand (idempotent); cleanup just makes it automatic.
2. Periodic sampler handing data back (interval + shared)
Why/when: Timer-driven sampling where main only ever wants the latest reading, not every sample.
Real-world: Polling a sensor every second into a value the main app (a web handler or display loop) reads on demand.
Main & background: { interval => $secs } paces the loop (the body needs no sleep); { shared => 1 } publishes the body's return value as a lossy latest value the parent reads with $w->value.
use strict;
use warnings;
use RPi::WiringPi;
use RPi::Const qw(:all);
my $pi = RPi::WiringPi->new;
my $pin = $pi->pin(4);
$pin->mode(INPUT); # once, in main
my $w = $pi->worker(sub { $pin->read }, { interval => 1, shared => 1 });
for (1 .. 5) {
my $latest = $w->value; # most recent sample, or undef until the first
print "latest: ", (defined $latest ? $latest : 'n/a'), "\n";
sleep 5;
}
$pi->cleanup;
The channel is lossy - the worker never blocks on a slow reader, so value() gives you the most recent sample and discards the ones you didn't read.
3. Streaming every result (results)
Why/when: When you need every value the worker produces, in order, not just the latest.
Real-world: A logger that records each reading, or a counter feeding an event-loop via select.
Main & background: { results => 1 } length-frames every defined return value back over a pipe. Drain it with $w->read (non-blocking), or select on $w->fh.
use strict;
use warnings;
use RPi::WiringPi;
use RPi::Const qw(:all);
my $pi = RPi::WiringPi->new;
my $pin = $pi->pin(4);
$pin->mode(INPUT); # once, in main
my $w = $pi->worker(sub { $pin->read }, { interval => 0.5, results => 1 });
for (1 .. 20) {
while (defined(my $v = $w->read)) { # drain everything pending
print "sample: $v\n";
}
sleep 1;
}
$pi->cleanup;
This is identical to $pi->background_interrupts' { results => 1 } channel.
4. A one-shot background task (once)
Why/when: A single background job - run it off the main path and let it exit on its own.
Real-world: Firing a one-shot solenoid pulse, or taking a single sensor reading, without blocking main.
Main & background: { once => 1 } runs the body exactly once; the child then exits and $w->running becomes false. You can still stop (idempotent) or just let $pi->cleanup clean up.
use strict;
use warnings;
use RPi::WiringPi;
use RPi::Const qw(:all);
my $pi = RPi::WiringPi->new;
my $pin = $pi->pin(5);
$pin->mode(OUTPUT); # once, in main
my $w = $pi->worker(sub {
$pin->write(HIGH);
select(undef, undef, undef, 0.2); # 200ms pulse
$pin->write(LOW);
}, { once => 1 });
# ... main carries on; the pulse fires in the background ...
$pi->cleanup; # the pulse has usually already finished
5. Several workers on distinct pins
Why/when: Multiple independent background tasks at once, each owning its own pin.
Real-world: A multi-channel relay board where each channel toggles on its own cadence while main runs the control logic.
Main & background: Configure every pin once in main, then start one worker per pin. Each runs independently and returns its own handle; all of them are tracked on the object and stopped together by $pi->cleanup.
use strict;
use warnings;
use RPi::WiringPi;
use RPi::Const qw(:all);
my $pi = RPi::WiringPi->new;
my @pins = map { my $p = $pi->pin($_); $p->mode(OUTPUT); $p } (23, 24, 25);
my @workers = map {
my $pin = $_;
$pi->worker(sub { $pin->write(HIGH); sleep 1; $pin->write(LOW); sleep 1 });
} @pins;
# ... main's own work ...
$pi->cleanup; # stops all @workers at once
Workers must drive distinct pins - see "THE SETUP-ONCE-IN-MAIN CONTRACT".
6. Shared memory - the opt-in ithread mechanism
Why/when: You specifically want to share memory directly between main and the worker (no IPC), and you have a threaded Perl.
Real-world: A counter or state machine the worker mutates and main reads in the same address space.
Main & background: { mechanism => 'thread' } runs the body in an ithread instead of a fork. It requires use threads (croaks otherwise) and rejects the results/shared pipe channels - share a :shared variable and serialize it with WiringPi::API::pi_lock/WiringPi::API::pi_unlock (keys 0-3) instead. There is no OO proxy for the lock - thread mode is a niche opt-in, so you call the WiringPi::API functions directly. $w->stop sets the stop flag and joins the thread.
use strict;
use warnings;
use threads; # required for mechanism => 'thread'
use threads::shared;
use RPi::WiringPi;
use WiringPi::API qw(pi_lock pi_unlock);
my $pi = RPi::WiringPi->new;
my $count :shared = 0;
my $w = $pi->worker(sub {
pi_lock(0);
$count++;
pi_unlock(0);
select(undef, undef, undef, 0.1);
}, { mechanism => 'thread' });
for (1 .. 5) {
pi_lock(0);
my $n = $count;
pi_unlock(0);
print "count: $n\n";
sleep 1;
}
$w->stop; # sets the stop flag and joins
Check for a threaded Perl with perl -V:useithreads (Raspberry Pi OS ships one). The fork default (scenarios 1-5) never locks and never needs threads.
REACTING TO INTERRUPTS IN THE BACKGROUND
$pi->worker is for running background work, not for reacting to GPIO edges. To handle an edge in the background - fire a callback even while main is blocked - use $pi->background_interrupts, the interrupt-side sibling of worker. It forks a single child that arms one or more pins and runs your callback on each edge, and returns a handle with the same stop/pid/running shape (plus arm/disarm):
use RPi::WiringPi;
use RPi::Const qw(:all);
my $pi = RPi::WiringPi->new;
my $pin = $pi->pin(4);
$pin->mode(INPUT);
my $h = $pi->background_interrupts(
[4, EDGE_RISING, \&on_edge, 0],
);
# ... main does its own work; the handler fires on its own ...
$pi->cleanup; # stops interrupts and workers together
sub on_edge { ... } # runs in the background child on each edge
The full interrupt story is in RPi::WiringPi::INTERRUPTS and the interrupt methods in RPi::WiringPi.
THE SETUP-ONCE-IN-MAIN CONTRACT
The rule that keeps concurrent GPIO safe: do all configuration once, in main, before starting any worker; afterwards each context does only steady-state I/O on distinct pins.
Construct the object and configure every pin (
$pi->pin($n),$pin->mode(...)) and any device once, in the parent, before the first$pi->worker.A fork worker inherits that configuration; a thread worker shares it.
Afterwards, workers may freely read/write on distinct pins. Never configure pins or set up devices concurrently - they read-modify-write shared registers.
For shared Perl data under
mechanism => 'thread', guard every access withWiringPi::API::pi_lock/WiringPi::API::pi_unlock(orthreads::shared'slock).A forked worker shares the
$piobject but must not tear it down: the process-guard incleanup()ensures only the parent stops workers and restores pins, so a worker exiting never disturbs the parent's state.
UNDER THE HOOD
These are the raw mechanisms $pi->worker packages up. You rarely need them directly; they are here to show what the method does and to cover cases it doesn't.
7. Manual fork
Why/when: The fork worker (scenario 1) without the method - full control over the child, at the cost of writing the loop, the signal handling and the reaping yourself.
Main & background: The child is a separate process: truly concurrent and crash-isolated, but it cannot touch main's variables - pass data back via a pipe, and reap it yourself.
use strict;
use warnings;
use RPi::WiringPi;
use RPi::Const qw(:all);
my $pi = RPi::WiringPi->new;
my $pin = $pi->pin(2);
$pin->mode(OUTPUT); # before fork
my $kid = fork // die "fork: $!";
if ($kid == 0) {
while (1) { # child: heartbeat forever
$pin->write(HIGH); sleep 1;
$pin->write(LOW); sleep 1;
}
exit 0;
}
# ... parent's own work ...
kill 'TERM', $kid; # on shutdown
waitpid $kid, 0;
$pi->worker(sub {...}) is exactly this - the fork, the loop, the TERM handler and the waitpid - done for you, with an idempotent stop, automatic reaping in $pi->cleanup, and an END-block safety net in WiringPi::API.
8. Raw ithreads (threads->create)
Why/when: The thread worker (scenario 6) without the method - when you want to manage the thread object yourself.
Main & background: The body runs in its own interpreter; it can't see main's lexicals - share only via :shared variables guarded by pi_lock/lock.
use strict;
use warnings;
use threads;
use threads::shared;
use RPi::WiringPi;
use WiringPi::API qw(pi_lock pi_unlock);
my $pi = RPi::WiringPi->new;
my $pin = $pi->pin(4);
$pin->mode(INPUT);
my $latest :shared = 0;
my $thr = threads->create(sub {
while (1) {
my $v = $pin->read;
pi_lock(0); $latest = $v; pi_unlock(0);
select(undef, undef, undef, 0.05);
}
});
# ... main reads $latest under pi_lock(0) ...
# To stop a hand-rolled thread you need your own shared flag + join;
# $pi->worker({mechanism=>'thread'}) provides exactly that.
$thr->detach;
$pi->worker(sub {...}, { mechanism => 'thread' }) wraps this with a shared stop flag and a clean stop/join, so you don't hand-roll the lifecycle.
ANTI-PATTERNS TO AVOID
Reaching for
use threadsfirst.$pi->workeris fork-based and needs no threaded Perl. Only usemechanism => 'thread'when you specifically want shared memory.Putting a loop inside the body.
$pi->workerowns the loop - the body is one pass. Use{ interval => $secs }for pacing and{ once => 1 }for a single pass; awhile (1)inside the body defeatsstop/once/interval.Concurrent pin configuration / device setup. Read-modify-write on shared registers; do them once, in main, before starting any worker. Only reads/writes on distinct pins are safe concurrently.
Expecting a fork worker to see main's variables. Separate memory - hand data back with
{ results => 1 }/{ shared => 1 }, not a shared Perl variable.Touching
:shareddata without a lock (thread mode). Guard every access withpi_lock/pi_unlock(orlock). A bare$shared++from two threads races.Combining
mechanism => 'thread'withresults/shared. Those are fork pipe channels and are rejected under thread mode - share a:sharedvariable withpi_lockinstead.
API REFERENCE FOR THESE EXAMPLES
RPi::WiringPi->new-
Construct the Pi object (GPIO/BCM numbering by default).
$pi->pin($num)/$pin->mode($mode)-
Register a pin and set its mode (
INPUT/OUTPUTfrom RPi::Const). $pin->write($val)/$pin->read-
Pin I/O;
readreturns the pin level (0/1). $pi->worker(\&body [, \%opts])-
Run
\&bodyin the background.\%opts:{once, interval, results, shared, mechanism}. Returns a handle$wwithstop/pid/running/read/fh/value. The handle is tracked on$piand stopped by$pi->cleanup. $pi->cleanup-
Normal teardown: stops every tracked worker, releases interrupts, and restores pins. Also runs from
DESTROY. WiringPi::API::pi_lock($key)/WiringPi::API::pi_unlock($key)-
Mutex (keys 0-3) for shared state under thread mode. Called directly - there is no OO proxy.
$pi->background_interrupts(...)-
Background edge handler (the interrupt-side sibling). Returns a handle
$h.
SEE ALSO
RPi::WiringPi, WiringPi::API, and WiringPi::API::WORKERS for the low-level functional API.
AUTHOR
Steve Bertrand, <steveb@cpan.org>