PERLTHRTUT(1perl)

πŸ“¦
version 5.44.0 Β· 2 versions
, in arch rolling

πŸ’– perlthrtut β€” the Tutorial on threads in Perl (you asked, bestie, and I delivered) πŸ’–

from the Perl Programmers Reference Guide, PERLTHRTUT(1perl)


✨ NAME ✨

perlthrtut β€” the whole entire tutorial on threads in Perl πŸ§΅πŸ’…


🌸 DESCRIPTION 🌸

okay so. This tutorial is about Perl interpreter threads β€” sometimes called ithreads β€” and babes, you need to know the vibe going in. In this model, every thread runs inside its own Perl interpreter, and any data that gets shared between threads has to be shared explicitly. No sneaky automatic sharing. Nothing. The user-level interface for ithreads is the threads class. 🫢

NOTE, and like, actually take this note seriously bestie: there was another, older Perl threading flavor called the 5.005 model, which used the Thread class. That old model was known to have problems, it was deprecated, and it was removed for release 5.10. You are strongly encouraged to migrate any existing 5.005 threads code to the new model as soon as possible. like, yesterday. 😭

Wanna know which threading flavour you have (or if you have literally neither)? Run:

perl -V

and go look at the Platform section. Here's the decoding chart, no cap:

  • useithreads=define β†’ congrats, you have ithreads πŸŽ‰
  • use5005threads=define β†’ you have 5.005 threads (the fossil πŸ’€)
  • neither β†’ you have no thread support built in at all, sorry babe πŸ™ˆ
  • both β†’ you are in trouble. fr fr.

In more modern times, the terms "ithreads" and "threads" are synonymous and get used interchangeably. Best practice, and this is the tea πŸ‘‘: use useithreads if your code depends on something specific about that implementation, and use usethreads if it doesn't. That way, in the very unlikely event that some new threading model comes along, your code is more protected from needing to change. The corresponding symbols at the C language level are USE_ITHREADS and USE_THREADS β€” these are currently totally synonymous, but not absolutely guaranteed to remain so. iykyk.

The threads and threads::shared modules are included in the core Perl distribution. Also, they're maintained as separate modules on CPAN, so you can go check there for any updates. πŸ’–


πŸ¦‹ What Is A Thread Anyway? πŸ¦‹

A thread is a flow of control through a program with a single execution point.

...sounds an awful lot like a process, doesn't it? And honestly, it should. Threads are one of the pieces of a process. Every process has at least one thread, and up until now, every process running Perl had only one thread. With 5.8, though, you can create extra threads. We're gonna show you how, when, and why. main character energy only ✨


πŸŽ€ Threaded Program Models πŸŽ€

There are three basic ways you can structure a threaded program. Which model you pick depends on what you need your program to actually do. And for many non-trivial threaded programs, you'll need to choose different models for different pieces of your program. Mix and match, it's giving versatility πŸ’…

πŸ‘‘ Boss/Worker

The boss/worker model usually has one boss thread and one or more worker threads. The boss thread gathers or generates tasks that need to be done, then parcels those tasks out to the appropriate worker thread. Very "I'll delegate, you execute" of it.

This model is common in GUI and server programs, where a main thread waits for some event and then passes that event to the appropriate worker threads for processing. Once the event has been passed on, the boss thread goes back to waiting for another event. (Iconic. Unbothered. Waiting.)

The boss thread does relatively little work. While tasks aren't necessarily performed faster than with any other method, it tends to have the best user-response times. 🌟

🌈 Work Crew

In the work crew model, several threads are created that do essentially the same thing to different pieces of data. It closely mirrors classical parallel processing and vector processors, where a large array of processors do the exact same thing to many pieces of data.

This model is particularly useful if the system running the program will distribute multiple threads across different processors. It can also be useful in ray tracing or rendering engines, where the individual threads can pass on interim results to give the user visual feedback. 🎨

🌊 Pipeline

The pipeline model divides up a task into a series of steps, and passes the results of one step on to the thread processing the next. Each thread does one thing to each piece of data and passes the results to the next thread in line. assembly line but make it perl πŸ’…

This model makes the most sense if you have multiple processors, so two or more threads will be executing in parallel β€” though it can often make sense in other contexts as well. It tends to keep the individual tasks small and simple, as well as allowing some parts of the pipeline to block (on I/O or system calls, for example) while other parts keep going. If you're running different parts of the pipeline on different processors, you may also take advantage of the caches on each processor. cache-friendly queen.

This model is also handy for a form of recursive programming where, rather than having a subroutine call itself, it instead creates another thread. Prime and Fibonacci generators both map well to this form of the pipeline model. (A version of a prime number generator is presented later on. πŸ‘€ stay tuned.)


πŸ’œ What kind of threads are Perl threads? πŸ’œ

If you have experience with other thread implementations, you might find that things aren't quite what you expect. And it is very important to remember, when dealing with Perl threads, that Perl Threads Are Not X Threads for all values of X. They aren't POSIX threads, or DecThreads, or Java's Green threads, or Win32 threads. There are similarities, and the broad concepts are the same, but if you start looking for implementation details you're going to be either disappointed or confused. Possibly both. πŸ˜­πŸ’€

This is not to say that Perl threads are completely different from everything that's ever come before. They're not. Perl's threading model owes a lot to other thread models, especially POSIX. Just as Perl is not C, though, Perl threads are not POSIX threads. So if you find yourself looking for mutexes, or thread priorities, it's time to step back a bit and think about what you want to do and how Perl can do it. πŸ§˜β€β™€οΈ

However, it's important to remember that Perl threads cannot magically do things unless your operating system's threads allow it. So if your system blocks the entire process on sleep(), Perl usually will, as well. Periodt.

Perl Threads Are Different. πŸ¦‹


πŸ“ Thread-Safe Modules πŸ“

The addition of threads has changed Perl's internals substantially. There are implications for people who write modules with XS code or external libraries. However β€” and this is the good part β€” since Perl data is not shared among threads by default, Perl modules stand a high chance of being thread-safe, or can be made thread-safe easily. Modules that are not tagged as thread-safe should be tested or code reviewed before being used in production code. 🫢

Not all modules that you might use are thread-safe, and you should always assume a module is unsafe unless the documentation says otherwise. This includes modules that are distributed as part of the core. Threads are a relatively new feature, and even some of the standard modules aren't thread-safe. πŸ’€

Even if a module is thread-safe, it doesn't mean that the module is optimized to work well with threads. A module could possibly be rewritten to utilize the new features in threaded Perl to increase performance in a threaded environment. glow-up potential ✨

If you're using a module that's not thread-safe for some reason, you can protect yourself by using it from one, and only one thread at all. If you need multiple threads to access such a module, you can use semaphores and lots of programming discipline to control access to it. Semaphores are covered in "Basic semaphores". πŸ“š

See also "Thread-Safety of System Libraries".

🧡 Thread Basics 🧡

The threads module is literally giving you the starter pack for writing threaded programs, bestie. In the next few sections we're gonna cover the basics β€” like, what you actually do to make a threaded program exist β€” and then we'll get into the extra features of threads that make threaded programming way easier. No cap. ✨


πŸ’– Basic Thread Support πŸ’–

So here's the tea: thread support is a Perl compile-time option. It's something that gets switched on or off when Perl is built at your site, not when your programs get compiled. So if your Perl wasn't compiled with thread support enabled? Yeah, any attempt to use threads is gonna fail. Periodt. 😭

Your programs can use the Config module to vibe check whether threads are even enabled. And if your program cannot run without them, you can say something like this:

use Config;
$Config{useithreads} or
    die('Recompile Perl with threads to run this program.');

And a possibly-threaded program using a possibly-threaded module might have code like this:

use Config;
use MyMod;

BEGIN {
    if ($Config{useithreads}) {
        # We have threads
        require MyMod_threaded;
        MyMod_threaded->import();
    } else {
        require MyMod_unthreaded;
        MyMod_unthreaded->import();
    }
}

Okay so, lowkey: code that runs both with and without threads is usually pretty messy, so the move is to isolate the thread-specific code in its own module. That's exactly what MyMod_threaded is in the example above, and it only gets imported if we're running on a threaded Perl. Clean. Organized. Iconic. πŸ’…


πŸŽ€ A Note about the Examples πŸŽ€

In a real situation, bestie, you should be careful that all threads are finished executing before the program exits. But that care has not been taken in these examples β€” for simplicity's sake, don't come for me. πŸ™ˆ

Running these examples as-is will produce error messages, usually caused by the fact that there are still threads running when the program exits. You should not be alarmed by this. It's fine. It's giving "chaotic but intentional."

πŸ¦‹ Creating Threads πŸ¦‹

The threads module hands you the tools you need to create new threads. Like any other module, you gotta tell Perl you want to use it β€” use threads; imports all the pieces you need to create basic threads. Slay.

The simplest, most straightforward way to create a thread is with create():

use threads;

my $thr = threads->create(\&sub1);

sub sub1 {
    print("In the thread\n");
}

The create() method takes a reference to a subroutine and creates a new thread that starts executing in that referenced subroutine. Then control passes both to the subroutine and to the caller. Two places at once, main character energy fr fr. ✨

If you need to, your program can pass parameters to the subroutine as part of the thread startup. Just toss the list of parameters into the threads->create() call, like this:

use threads;

my $Param3 = 'foo';
my $thr1 = threads->create(\&sub1, 'Param 1', 'Param 2', $Param3);
my @ParamList = (42, 'Hello', 3.14);
my $thr2 = threads->create(\&sub1, @ParamList);
my $thr3 = threads->create(\&sub1, qw(Param1 Param2 Param3));

sub sub1 {
    my @InboundParameters = @_;
    print("In the thread\n");
    print('Got parameters >', join('<>',@InboundParameters), "<\n");
}

That last example is showing off another feature of threads: you can spawn off several threads using the same subroutine. Each thread executes the same subroutine, but in a separate thread, with a separate environment and potentially separate arguments. Three threads, one sub, zero drama. πŸ’–

Oh and btw β€” new() is a synonym for create().


🌸 Waiting For A Thread To Exit 🌸

Since threads are also subroutines, they can return values. So to wait for a thread to exit and extract any values it might have returned, you use the join() method:

use threads;

my ($thr) = threads->create(\&sub1);

my @ReturnData = $thr->join();
print('Thread returned ', join(', ', @ReturnData), "\n");

sub sub1 { return ('Fifty-six', 'foo', 2); }

In the example above, join() returns as soon as the thread ends. And it does a lot more than just vibe there β€” in addition to waiting for a thread to finish and gathering up any values the thread might have returned, join() also performs any OS cleanup necessary for the thread. That cleanup might be important, especially for long-running programs that spawn lots of threads. So if you don't want the return values and don't wanna wait for the thread to finish, you should call the detach() method instead β€” details coming right up next. 🫢

NOTE: In the example above, the thread returns a list, which means the thread creation call has to be made in list context (i.e., my ($thr)). See "$thr->join()" in threads and "THREAD CONTEXT" in threads for more details on thread context and return values.

πŸ’œ Ignoring A Thread πŸ’œ

Okay so join() does three things: it waits for a thread to exit, it cleans up after it, and it returns any data the thread may have produced. Three-for-one, iconic.

But what if you're just… not interested in the thread's return values? And you don't really care when the thread finishes? All you want is for the thread to get cleaned up after when it's done. Same, honestly.

In that case, you use the detach() method. Once a thread is detached, it'll run until it's finished, and then Perl will clean up after it automatically. No babysitting required. 🧚

use threads;

my $thr = threads->create(\&sub1);   # Spawn the thread

$thr->detach();   # Now we officially don't care any more

sleep(15);        # Let thread run for a while

sub sub1 {
    my $count = 0;
    while (1) {
        $count++;
        print("\$count is $count\n");
        sleep(1);
    }
}
⚠️ Heads up though: once a thread is detached, it may not be joined, and any return data it might have produced (if it was done and waiting for a join) is lost. Gone. Poof. So detach responsibly, bestie. πŸ’€

Oh and detach() can also be called as a class method, which lets a thread detach itself:

use threads;

my $thr = threads->create(\&sub1);

sub sub1 {
    threads->detach();
    # Do more work
}

Self-detaching? That's independent queen behavior. πŸ‘‘


πŸ”₯ Process and Thread Termination πŸ”₯

With threads you gotta be careful to make sure they all get a chance to run to completion β€” assuming that's what you actually want, of course.

Here's the crucial bit: an action that terminates a process will terminate all running threads. die() and exit() have this property, and perl does an exit when the main thread exits β€” maybe implicitly, by you just falling off the end of your code β€” even if that's not what you wanted. Rude, but that's the deal.

As an example of this case, this code prints the message "Perl exited with active threads: 2 running and unjoined":

use threads;
my $thr1 = threads->new(\&thrsub, "test1");
my $thr2 = threads->new(\&thrsub, "test2");
sub thrsub {
   my ($message) = @_;
   sleep 1;
   print "thread $message\n";
}

But when the following lines are added at the end:

$thr1->join();
$thr2->join();

it prints two lines of output β€” a perhaps more useful outcome. 😌✨ Now that's what I call a glow-up.

🌸 Threads And Data 🌸

okay bestie, we did the basics, now we get to the juicy part: data. threading adds a whole couple of complications to data access that your poor little non-threaded programs never had to deal with. strap in πŸŽ€


✨ Shared And Unshared Data ✨

so here's the biggest difference between Perl ithreads and the old 5.005 style threading β€” and honestly, between ithreads and most other threading systems out there, periodt. by default, NO data is shared. when a new Perl thread gets created, all the data associated with the current thread gets copied into the new thread, and after that it's private to that new thread, no cap! πŸ’…

the vibe is kinda like what happens when a Unix process forks, except here the data just gets copied to a different part of memory within the same process, rather than a real fork actually happening. iykyk.

but like… to actually make use of threading, you usually want the threads to share at least some data between themselves, right? and that's what the threads::shared module and the ":shared" attribute are for 🫢:

use threads;
use threads::shared;

my $foo :shared = 1;
my $bar = 1;
threads->create(sub { $foo++; $bar++; })->join();

print("$foo\n");  # Prints 2 since $foo is shared
print("$bar\n");  # Prints 1 since $bar is not shared

with a shared array, all the array's elements are shared. with a shared hash, all the keys and values are shared. this does place restrictions on what you're allowed to assign to shared array and hash elements: only simple values, or references to shared variables, are allowed. why? so that a private variable can't accidentally become shared, obviously. a bad assignment will cause the thread to die. πŸ’€ example:

use threads;
use threads::shared;

my $var          = 1;
my $svar :shared = 2;
my %hash :shared;

... create some threads ...

$hash{a} = 1;       # All threads see exists($hash{a})
                    # and $hash{a} == 1
$hash{a} = $var;    # okay - copy-by-value: same effect as previous
$hash{a} = $svar;   # okay - copy-by-value: same effect as previous
$hash{a} = \$svar;  # okay - a reference to a shared variable
$hash{a} = \$var;   # This will die
delete($hash{a});   # okay - all threads will see !exists($hash{a})

quick note: a shared variable guarantees that if two or more threads try to modify it at the same time, the internal state of the variable won't get corrupted. but that's it. there are no guarantees beyond that, and the next section explains why πŸ‘€


😭 Thread Pitfalls: Races 😭

threads bring a new set of useful tools, sure, but they also bring a number of pitfalls. and one pitfall is the race condition:

use threads;
use threads::shared;

my $x :shared = 1;
my $thr1 = threads->create(\&sub1);
my $thr2 = threads->create(\&sub2);

$thr1->join();
$thr2->join();
print("$x\n");

sub sub1 { my $foo = $x; $x = $foo + 1; }
sub sub2 { my $bar = $x; $x = $bar + 1; }

so… what do you think $x is gonna be? 😳 the answer, unfortunately, is it depends. both sub1() and sub2() access the global variable $x, once to read and once to write. depending on factors ranging from your thread implementation's scheduling algorithm all the way to the literal phase of the moon, $x can be 2 or 3. not me getting stressed about the moon 😭

race conditions are caused by unsynchronized access to shared data. without explicit synchronization, there's just no way to be sure nothing happened to the shared data between the time you access it and the time you update it. even this lil simple code fragment has the possibility of error:

use threads;
my $x :shared = 2;
my $y :shared;
my $z :shared;
my $thr1 = threads->create(sub { $y = $x; $x = $y + 1; });
my $thr2 = threads->create(sub { $z = $x; $x = $z + 1; });
$thr1->join();
$thr2->join();

two threads both access $x. each thread can potentially be interrupted at any point, or be executed in any order. at the end, $x could be 3 or 4, and both $y and $z could be 2 or 3. it's giving chaos honestly πŸ™ˆ

and listen β€” even "$x += 5" or "$x++" are not guaranteed to be atomic. i know. devastating.

whenever your program accesses data or resources that can also be accessed by other threads, you have to take steps to coordinate that access, or you're risking data inconsistency and race conditions. note that Perl will protect its own internals from your race conditions β€” but it will not protect you from you. πŸ’€

πŸ”₯ Synchronization and control πŸ”₯

Perl gives you a bunch of mechanisms to coordinate the interactions between your threads and their data, so you can dodge race conditions and all that mess. some of these are designed to resemble the common techniques used in thread libraries like "pthreads"; others are Perl-specific. and honestly? often the standard techniques are clumsy and hard to get right (looking at you, condition waits πŸ‘€). where possible, it's usually easier to use the Perlish techniques, like queues, which take some of the hard work out of it entirely. queen behavior πŸ’…


πŸ‘‘ Controlling access: lock() πŸ‘‘

the lock() function takes a shared variable and puts a lock on it. no other thread may lock that variable until the variable gets unlocked by whichever thread is holding the lock. and unlocking happens automatically when the locking thread exits the block that contains the call to the lock() function. obsessed with the convenience fr fr.

using lock() is straightforward: this example has several threads doing some calculations in parallel, and occasionally updating a running total:

use threads;
use threads::shared;

my $total :shared = 0;

sub calc {
    while (1) {
        my $result;
        # (... do some calculations and set $result ...)
        {
            lock($total);  # Block until we obtain the lock
            $total += $result;
        } # Lock implicitly released at end of scope
        last if $result == 0;
    }
}

my $thr1 = threads->create(\&calc);
my $thr2 = threads->create(\&calc);
my $thr3 = threads->create(\&calc);
$thr1->join();
$thr2->join();
$thr3->join();
print("total=$total\n");

lock() blocks the thread until the variable being locked is available. when lock() returns, your thread can be sure that no other thread can lock that variable until the block containing the lock exits. periodt πŸŽ€

super important to note: locks do not prevent access to the variable in question β€” they only prevent lock attempts. this is in keeping with Perl's longstanding tradition of courteous programming, and with the advisory file locking that flock() gives you. πŸ’–

you can lock arrays and hashes too, not just scalars. locking an array, though, will not block subsequent locks on array elements β€” just lock attempts on the array itself. (iykyk, sneaky.)

locks are recursive, which means it's totally okay for a thread to lock a variable more than once. the lock will last until the outermost lock() on the variable goes out of scope. like so:

my $x :shared;
doit();

sub doit {
    {
        {
            lock($x); # Wait for lock
            lock($x); # NOOP - we already have the lock
            {
                lock($x); # NOOP
                {
                    lock($x); # NOOP
                    lockit_some_more();
                }
            }
        } # *** Implicit unlock here ***
    }
}

sub lockit_some_more {
    lock($x); # NOOP
} # Nothing happens here

and note that there is no unlock() function β€” the only way to unlock a variable is to let it go out of scope. it's giving "you don't break up with a lock, the lock just leaves" πŸ¦‹

a lock can be used to guard the data contained within the variable being locked, or it can be used to guard something else entirely β€” like a section of code. in that latter case, the variable in question doesn't hold any useful data and exists purely for the purpose of being locked. in this respect, the variable behaves like the mutexes and basic semaphores of traditional thread libraries. iconic ✨


πŸ’€ A Thread Pitfall: Deadlocks πŸ’€

locks are a handy tool to synchronize access to data, and using them properly is the key to safe shared data. unfortunately, locks aren't without their dangers, especially when multiple locks get involved. consider this:

use threads;
use threads::shared;

my $x :shared = 4;
my $y :shared = 'foo';
my $thr1 = threads->create(sub {
    lock($x);
    sleep(20);
    lock($y);
});
my $thr2 = threads->create(sub {
    lock($y);
    sleep(20);
    lock($x);
});
$thr1->join();
$thr2->join();

this program will probably hang until you kill it 😭 the only way it won't hang is if one of the two threads acquires both locks first. a guaranteed-to-hang version is more complicated, but the principle is the same.

here's what's happening: the first thread grabs a lock on $x, then, after a pause during which the second thread has probably had time to do some work, tries to grab a lock on $y. meanwhile, the second thread grabs a lock on $y, then later tries to grab a lock on $x. so the second lock attempt for both threads will block, each one waiting for the other to release its lock. πŸ’€ forever. mutual situationship.

this condition is called a deadlock, and it occurs whenever two or more threads are trying to get locks on resources that the others own. each thread will block, waiting for the other to release a lock on a resource. that never happens, though, since the thread holding the resource is itself waiting for a lock to be released. truly a circular nightmare.

there are a number of ways to handle this sort of problem. the best way is to always have all threads acquire locks in the exact same order. if, for example, you lock variables $x, $y, and $z, always lock $x before $y, and $y before $z. it's also best to hold on to locks for as short a period of time as possible, to minimize the risks of deadlock. short king locks only πŸ‘‘

the other synchronization primitives described below can suffer from similar problems, jsyk.


πŸ“ Queues: Passing Data Around πŸ“

a queue is a special thread-safe object that lets you put data in one end and take it out the other, without having to worry about synchronization issues at all. they're pretty straightforward, and they look like this:

use threads;
use Thread::Queue;

my $DataQueue = Thread::Queue->new();
my $thr = threads->create(sub {
    while (my $DataElement = $DataQueue->dequeue()) {
        print("Popped $DataElement off the queue\n");
    }
});

$DataQueue->enqueue(12);
$DataQueue->enqueue("A", "B", "C");
sleep(10);
$DataQueue->enqueue(undef);
$thr->join();

the way that this just works without you babysitting locks… we love to see it πŸ’œ

You spin up a queue with Thread::Queue->new(). Then you can pile lists of scalars onto the end with enqueue(), and pop scalars off the front with dequeue(). A queue has no fixed size, and it'll grow as needed to hold literally everything you push onto it. πŸ’–

If a queue is empty, dequeue() blocks until some other thread enqueues something. Which is exactly why queues are ideal for event loops and other communication between threads. Chef's kiss. πŸ˜šπŸ‘Œ


Semaphores: Synchronizing Data Access πŸ”’βœ¨

Semaphores are a kind of generic locking mechanism, bestie. In their most basic form they behave very much like lockable scalars β€” except they can't hold data, and they must be explicitly unlocked. In their advanced form? They act like a kind of counter, and can let multiple threads hold the lock at any one time. Glow up. πŸ’…

Basic semaphores

Semaphores have two methods: down() and up(). down() decrements the resource count, while up() increments it. Calls to down() will block if the semaphore's current count would decrement below zero. This program gives a quick demonstration:

use threads;
use Thread::Semaphore;

my $semaphore = Thread::Semaphore->new();
my $GlobalVariable :shared = 0;

$thr1 = threads->create(\&sample_sub, 1);
$thr2 = threads->create(\&sample_sub, 2);
$thr3 = threads->create(\&sample_sub, 3);

sub sample_sub {
    my $SubNumber = shift(@_);
    my $TryCount = 10;
    my $LocalCopy;
    sleep(1);
    while ($TryCount--) {
        $semaphore->down();
        $LocalCopy = $GlobalVariable;
        print("$TryCount tries left for sub $SubNumber "
             ."(\$GlobalVariable is $GlobalVariable)\n");
        sleep(2);
        $LocalCopy++;
        $GlobalVariable = $LocalCopy;
        $semaphore->up();
    }
}

$thr1->join();
$thr2->join();
$thr3->join();

The three invocations of the subroutine all operate in sync. The semaphore, though, makes sure that only one thread is accessing the global variable at once. No fights, no chaos, just vibes. 🧚

Advanced Semaphores

By default, semaphores behave like locks, letting only one thread down() them at a time. However, there are other uses for semaphores. πŸ‘‘

Each semaphore has a counter attached to it. By default, semaphores are created with the counter set to one, down() decrements the counter by one, and up() increments by one. But we can override any or all of these defaults simply by passing in different values:

use threads;
use Thread::Semaphore;

my $semaphore = Thread::Semaphore->new(5);
                # Creates a semaphore with the counter set to five

my $thr1 = threads->create(\&sub1);
my $thr2 = threads->create(\&sub1);

sub sub1 {
    $semaphore->down(5); # Decrements the counter by five
    # Do stuff here
    $semaphore->up(5); # Increment the counter by five
}

$thr1->detach();
$thr2->detach();

If down() attempts to decrement the counter below zero, it blocks until the counter is large enough. Note that while a semaphore can be created with a starting count of zero, any up() or down() always changes the counter by at least one, and so $semaphore->down(0) is the same as $semaphore->down(1). Lowkey a little cursed but okay. πŸ™ˆ

The question, of course, is why would you do something like this? Why create a semaphore with a starting count that's not one, or why decrement or increment it by more than one? The answer is resource availability. Many resources that you want to manage access for can be safely used by more than one thread at once. ✨

For example, let's take a GUI driven program. It has a semaphore that it uses to synchronize access to the display, so only one thread is ever drawing at once. Handy, but of course you don't want any thread to start drawing until things are properly set up. In this case, you can create a semaphore with a counter set to zero, and up it when things are ready for drawing.

Semaphores with counters greater than one are also useful for establishing quotas. Say, for example, that you have a number of threads that can do I/O at once. You don't want all the threads reading or writing at once though, since that can potentially swamp your I/O channels, or deplete your process's quota of filehandles. You can use a semaphore initialized to the number of concurrent I/O requests (or open files) that you want at any one time, and have your threads quietly block and unblock themselves. Teamwork fr fr. 🫢

Larger increments or decrements are handy in those cases where a thread needs to check out or return a number of resources at once.

Waiting for a Condition

The functions cond_wait() and cond_signal() can be used in conjunction with locks to notify co-operating threads that a resource has become available. They are very similar in use to the functions found in "pthreads". However for most purposes, queues are simpler to use and more intuitive. See threads::shared for more details. πŸ‘€

Giving up control

There are times when you may find it useful to have a thread explicitly give up the CPU to another thread. You may be doing something processor-intensive and want to make sure that the user-interface thread gets called frequently. Regardless, there are times that you might want a thread to give up the processor. It's giving "here, you take the aux cord." 🎧

Perl's threading package provides the yield() function that does this. yield() is pretty straightforward, and works like this:

use threads;

sub loop {
    my $thread = shift;
    my $foo = 50;
    while($foo--) { print("In thread $thread\n"); }
    threads->yield();
    $foo = 50;
    while($foo--) { print("In thread $thread\n"); }
}

my $thr1 = threads->create(\&loop, 'first');
my $thr2 = threads->create(\&loop, 'second');
my $thr3 = threads->create(\&loop, 'third');

It is important to remember that yield() is only a hint to give up the CPU β€” it depends on your hardware, OS and threading libraries what actually happens. On many operating systems, yield() is a no-op. Therefore it is important to note that one should not build the scheduling of the threads around yield() calls. It might work on your platform but it won't work on another platform. 🚨

Bestie. Do not build your scheduling on yield(). It's a hint, not a promise. Cross-platform betrayal is a real thing. πŸ’€

✨ General Thread Utility Routines ✨

We've covered the workhorse parts of Perl's threading package, and with these tools you should be well on your way to writing threaded code and packages. There are a few useful little pieces that didn't really fit in anyplace else. Lil bonus round. πŸŽ€

What Thread Am I In?

The threads->self() class method provides your program with a way to get an object representing the thread it's currently in. You can use this object in the same way as the ones returned from thread creation. Main character identification moment. πŸ’…

Thread IDs

tid() is a thread object method that returns the thread ID of the thread the object represents. Thread IDs are integers, with the main thread in a program being 0. Currently Perl assigns a unique TID to every thread ever created in your program, assigning the first thread to be created a TID of 1, and increasing the TID by 1 for each new thread that's created. When used as a class method, threads->tid() can be used by a thread to get its own TID. πŸ”’

Are These Threads The Same?

The equal() method takes two thread objects and returns true if the objects represent the same thread, and false if they don't.

Thread objects also have an overloaded == comparison so that you can do comparison on them as you would with normal objects. Slay. ⭐

What Threads Are Running?

threads->list() returns a list of thread objects, one for each thread that's currently running and not detached. Handy for a number of things, including cleaning up at the end of your program (from the main Perl thread, of course):

# Loop through all the threads
foreach my $thr (threads->list()) {
    $thr->join();
}

If some threads have not finished running when the main Perl thread ends, Perl will warn you about it and die, since it is impossible for Perl to clean up itself while other threads are running. 😭

NOTE: The main Perl thread (thread 0) is in a detached state, and so does not appear in the list returned by threads->list().

πŸ’– A Complete Example πŸ’–

Still a lil lost? Honestly same, bestie. Time for an actual program so you can see the stuff we've been yapping about. This one finds prime numbers using threads. 🧚✨

1 #!/usr/bin/perl
2 # prime-pthread, courtesy of Tom Christiansen
3
4 use v5.36;
5
6 use threads;
7 use Thread::Queue;
8
9 sub check_num ($upstream, $cur_prime) {
10     my $kid;
11     my $downstream = Thread::Queue->new();
12     while (my $num = $upstream->dequeue()) {
13         next unless ($num % $cur_prime);
14         if ($kid) {
15             $downstream->enqueue($num);
16         } else {
17             print("Found prime: $num\n");
18             $kid = threads->create(\&check_num, $downstream, $num);
19             if (! $kid) {
20                 warn("Sorry.  Ran out of threads.\n");
21                 last;
22             }
23         }
24     }
25     if ($kid) {
26         $downstream->enqueue(undef);
27         $kid->join();
28     }
29 }
30
31 my $stream = Thread::Queue->new(3..1000, undef);
32 check_num($stream, 2);

Okay so what is this doing? It's using the pipeline model to generate prime numbers. Every thread in the pipeline has three things going on:

  • an input queue</> that feeds it numbers to be checked
  • a prime number</> it's personally responsible for
  • an output queue</> where it funnels the numbers that failed its check

And here's the little logic slay ✨: if a thread has a number that failed its check and it has no child thread yet, then bestie β€” that thread just found a new prime. So it spins up a brand-new child thread for that prime and yeets it onto the end of the pipeline. πŸ‘‘

Ngl that probably sounds more confusing than it actually is, so let's walk this thing piece by piece and see what's up. (And for anyone out there trying to remember exactly what a prime number is: it's a number that's only evenly divisible by itself and 1. 🫢 There you go.)

The bulk of the work is done by the check_num() subroutine, which takes a reference to its input queue plus a prime number that it's responsible for. We create a new queue (line 11) and reserve a scalar for the thread that we're likely to create later (line 10).

The while loop from line 12 to line 24 grabs a scalar off the input queue and checks it against the prime this thread is responsible for. Line 13 checks whether there's a remainder when we divide the number-to-be-checked by our prime. If there is a remainder, then that number must not be evenly divisible by our prime, so we gotta either pass it along to the next thread if we've created one (line 15), or create a new thread if we haven't.

The new thread creation is line 18. We hand it a reference to the queue we've created, plus the prime number we've found. Then in lines 19 through 22 we check to make sure our new thread actually got created β€” and if it didn't, we stop checking any remaining numbers in the queue. πŸ™ˆ

Finally, once the loop terminates (because we got a 0 or "undef" in the queue, which is the lil note telling us to terminate), we pass that notice on to our child, and we wait for it to exit if we've created a child (lines 25 and 28).

Meanwhile, back over in the main thread, we first create a queue (line 31) and queue up all the numbers from 3 to 1000 for checking, plus a termination notice. Then all we gotta do to get the ball rolling is pass the queue and the first prime to the check_num() subroutine (line 32).

And that's literally how it works. It's pretty simple β€” as with many Perl programs, the explanation is way longer than the program itself. 😭


🌈 Different implementations of threads 🌈

Some background on thread implementations, but from the operating system's point of view. There are three basic categories of threads: user-mode threads, kernel threads, and multiprocessor kernel threads.

User-mode threads are threads that live entirely within a program and its libraries. In this model, the OS knows nothing about threads. As far as it's concerned, your process is just a process. That's it.

This is the easiest way to implement threads, and it's the way most OSes start out. The big disadvantage? Since the OS knows nothing about threads, if one thread blocks then they all do. And typical blocking activities include most system calls, most I/O, and things like sleep().

Kernel threads are the next step in thread evolution. The OS knows about kernel threads and makes allowances for them. The main difference between a kernel thread and a user-mode thread is blocking. With kernel threads, things that block a single thread don't block the other threads. Not the case with user-mode threads, where the kernel blocks at the process level and not the thread level. πŸ’€

This is a big step forward, and it can give a threaded program quite a performance boost over non-threaded programs. Threads that block performing I/O, for example, won't block threads that are doing other things. Each process still has only one thread running at once, though, regardless of how many CPUs a system might have.

And since kernel threading can interrupt a thread at any time, they will uncover some of the implicit locking assumptions you may have made in your program. For example, something as simple as $x = $x + 2 can behave unpredictably with kernel threads if $x is visible to other threads β€” because another thread may have changed $x between the time it was fetched on the right hand side and the time the new value gets stored. Not me finding a race condition in a one-liner. πŸ™ˆ

Multiprocessor kernel threads are the final step in thread support. With multiprocessor kernel threads on a machine with multiple CPUs, the OS may schedule two or more threads to run simultaneously on different CPUs.

This can give a serious performance boost to your threaded program, since more than one thread will be executing at the same time. The tradeoff though? Any of those nagging synchronization issues that might not have shown up with basic kernel threads will now appear with a vengeance. 😭

On top of all these different levels of OS involvement in threads, different OSes (and different thread implementations for a particular OS) allocate CPU cycles to threads in different ways.

Cooperative multitasking systems have running threads give up control if one of two things happens. If a thread calls a yield function, it gives up control. It also gives up control if the thread does something that would cause it to block, such as perform I/O. In a cooperative multitasking implementation, one thread can starve all the others for CPU time if it so chooses. Rude honestly.

Preemptive multitasking systems interrupt threads at regular intervals while the system decides which thread should run next. In a preemptive multitasking system, one thread usually won't monopolize the CPU. Periodt. πŸ‘‘

On some systems, there can be cooperative and preemptive threads running simultaneously. (Threads running with realtime priorities often behave cooperatively, for example, while threads running at normal priorities behave preemptively.)

Most modern operating systems support preemptive multitasking nowadays.


⚑ Performance considerations ⚑

The main thing to keep in your head when comparing Perl's ithreads to other threading models is this: for each new thread created, a complete copy of all the variables and data of the parent thread has to be taken. So thread creation can be quite expensive β€” both in memory usage and in the time spent creating them. 🫠

The ideal way to reduce these costs is to have a relatively short number of long-lived threads, all created fairly early on (like, before the base thread has accumulated too much data). Of course that's not always possible, so compromises have to be made. But hey β€” once a thread has been created, its performance and extra memory usage should be little different than ordinary code. ✨

Also note that under the current implementation, shared variables use a little more memory and are a little slower than ordinary variables.


πŸŒ€ Process-scope Changes πŸŒ€

Okay so here's a thing: even though threads themselves are separate execution threads, and Perl data is thread-private unless explicitly shared, threads can still affect process-scope state β€” which affects all the threads. 😬

The most common example is changing the current working directory with chdir(). One thread calls chdir(), and the working directory of all the threads changes.

An even more drastic example of a process-scope change is chroot(): the root directory of all the threads changes, and no thread can undo it (as opposed to chdir()).

Further examples of process-scope changes include umask() and changing uids and gids.

Thinking of mixing fork() and threads? Bestie, please lie down and wait until the feeling passes. πŸ™ˆ Be aware that the semantics of fork() vary between platforms. For example, some Unix systems copy all the current threads into the child process, while others only copy the thread that called fork(). You have been warned!

Similarly, mixing signals and threads may be problematic. Implementations are platform-dependent, and even the POSIX semantics may not be what you expect (and Perl doesn't even give you the full POSIX API). For example, there is no way to guarantee that a signal sent to a multi-threaded Perl application will get intercepted by any particular thread. (However, a recently added feature does provide the capability to send signals between threads. See "THREAD SIGNALLING" in threads for more details.)


πŸ›‘οΈ Thread-Safety of System Libraries πŸ›‘οΈ

Whether C library calls are thread-safe is outside the control of Perl. Undefined behavior will happen if unsafe ones are used during multi-thread operation. See "Dealing with embedded perls and threads" in perlclib.


πŸŽ€ Conclusion πŸŽ€

A complete thread tutorial could fill a book (and honestly, it has β€” many times), but with everything we've covered in this introduction, you should be well on your way to becoming a threaded Perl expert. Slay. Go forth. πŸ’…βœ¨


πŸ‘‘ SEE ALSO πŸ‘‘


πŸ“š Bibliography πŸ“š

okay bestie, you've read all the thread lore, now you get the reading list πŸ’– this lil bibliography is courtesy of JΓΌrgen Christoffel β€” yes, THE JΓΌrgen Christoffel, we love a king who shares his sources 🫢

✨ Introductory Texts ✨

  • Birrell, Andrew D. An Introduction to Programming with Threads. Digital Equipment Corporation, 1989, DEC-SRC Research Report #35 online as https://www.hpl.hp.com/techreports/Compaq-DEC/SRC-RR-35.pdf β€” and the manpage itself literally says highly recommended, so like, that's not me saying it, that's canon πŸ’…
  • Robbins, Kay. A., and Steven Robbins. Practical Unix Programming: A Guide to Concurrency, Communication, and Multithreading. Prentice-Hall, 1996.
  • Lewis, Bill, and Daniel J. Berg. Multithreaded Programming with Pthreads. Prentice Hall, 1997, ISBN 0-13-443698-9 β€” described as a well-written introduction to threads, so ngl it's giving beginner bestie energy 🌸
  • Nelson, Greg (editor). Systems Programming with Modula-3. Prentice Hall, 1991, ISBN 0-13-590464-1.
  • Nichols, Bradford, Dick Buttlar, and Jacqueline Proulx Farrell. Pthreads Programming. O'Reilly & Associates, 1996, ISBN 156592-115-1 β€” this one covers POSIX threads, so if pthreads is your roman empire, here u go πŸŽ€
  • Boykin, Joseph, David Kirschen, Alan Langerman, and Susan LoVerso. Programming under Mach. Addison-Wesley, 1994, ISBN 0-201-52739-1.
  • Tanenbaum, Andrew S. Distributed Operating Systems. Prentice Hall, 1995, ISBN 0-13-219908-4 β€” flagged as a great textbook, and honestly? slay πŸ“–
  • Silberschatz, Abraham, and Peter B. Galvin. Operating System Concepts, 4th ed. Addison-Wesley, 1995, ISBN 0-201-59292-4

🌈 Other References 🌈

  • Arnold, Ken and James Gosling. The Java Programming Language, 2nd ed. Addison-Wesley, 1998, ISBN 0-201-31006-6.
  • the comp.programming.threads FAQ, at http://www.serpentine.com/~bos/threads-faq/ πŸ‘‘
  • Le Sergent, T. and B. Berthomieu. "Incremental MultiThreaded Garbage Collection on Virtually Shared Memory Architectures" in Memory Management: Proc. of the International Workshop IWMM 92, St. Malo, France, September 1992, Yves Bekkers and Jacques Cohen, eds. Springer, 1992, ISBN 3540-55940-X β€” this one is the real-life thread applications entry, so if you wanted to see threads actually out here in the wild, boom πŸ¦‹
  • Artur Bergman, "Where Wizards Fear To Tread", June 11, 2002, http://www.perl.com/pub/a/2002/06/11/threads.html ✨

πŸ’– Acknowledgements πŸ’–

shoutout time, and these are in no particular order btw: Chaim Frenkel, Steve Fink, Gurusamy Sarathy, Ilya Zakharevich, Benjamin Sugars, JΓΌrgen Christoffel, Joshua Pritikin, and Alan Burlison β€” all of them helped reality-check and polish this article, which is the most organized-group-chat thing ever 😭

and a BIG thanks to Tom Christiansen for his rewrite of the prime number generator 🧚 because the prime number generator rewrite is lowkey the main character of this whole doc πŸ’œ


πŸ‘‘ AUTHOR πŸ‘‘

Dan Sugalski dan@sidhe.org

Slightly modified by Arthur Bergman to fit the new thread model/module.

Reworked slightly by JΓΆrg Walter jwalt@cpan.org to be more concise about thread-safety of Perl code.

Rearranged slightly by Elizabeth Mattijsen liz@dijkmat.nl to put less emphasis on yield().


πŸ“ Copyrights πŸ“

The original version of this article originally appeared in The Perl Journal #10, and is copyright 1998 The Perl Journal. It appears courtesy of Jon Orwant and The Perl Journal. This document may be distributed under the same terms as Perl itself. πŸ’…


ok that's the whole thing bestie, you are now fully thread-pilled 🧡✨ go forth and threads->create() responsibly, no cap 🫢

and that's a wrap on perl v5.44.0, dated 2026-10-17 β€” the PERLTHRTUT(1perl) page, read entirely, by you, just now. iconic behavior fr fr πŸ’–πŸŽ€πŸŒΈ

πŸ“¦ where this page comes from
  • arch rolling: perl 5.44.0-1 Β· Artistic-1.0-Perl AND GPL-1.0-or-later
  • πŸ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms