Skip to content

Instantly share code, notes, and snippets.

@manwar
Last active March 1, 2025 17:11
Livelock in Perl and Python.

-- Back to Index --


Livelock is a concurrency issue where two or more proceess or threads are actively responding to each other's action but are unable to make progress toward completing their tasks. Unlike a Deadlock, where process are blocked and waiting indefinitely.

Let's do this In Perl first.

[Source Code]

#!/usr/bin/env perl

use v5.38;
use threads;
use threads::shared;
use Time::HiRes qw(sleep);

my $lock_a  :shared = 1;
my $lock_b  :shared = 1;
my $lock_c  :shared = 1;
my $counter :shared = 9;

sub try_lock($this) {
    {
        lock($$this);
        return 1 if $$this;
    }
    sleep(rand() / 10);
    return 0;
}

sub worker($name, $first, $second) {
    while ($counter > 0) {
        {
            lock($$first);
            if ($$first && try_lock($second)) {
                if ($counter > 0) {
                    $counter--;
                    say "$name took the turn, counter is $counter now.";
                }
            }
        }
        sleep(rand() / 10);
    }
}

my @threads;
push @threads, threads->create(\&worker, 'Thread 1', \$lock_a, \$lock_b);
push @threads, threads->create(\&worker, 'Thread 2', \$lock_b, \$lock_c);
push @threads, threads->create(\&worker, 'Thread 3', \$lock_c, \$lock_a);
$_->join foreach @threads;

NOTE: threads->create() creates a new thread and immediately start executing the worker routine. $_->join makes the main process to wait until the thread finished executing the worker.

See the action now:

$ perl livelock.pl
Thread 1 took the turn, counter is 8 now.
Thread 2 took the turn, counter is 7 now.
Thread 3 took the turn, counter is 6 now.
Thread 2 took the turn, counter is 5 now.
Thread 3 took the turn, counter is 4 now.
Thread 2 took the turn, counter is 3 now.
Thread 1 took the turn, counter is 2 now.
Thread 2 took the turn, counter is 1 now.
Thread 3 took the turn, counter is 0 now.
$

Time to do the same in Python:

[Source Code]

#!/usr/bin/env python3

import time
import threading
from random import random

lock_a  = threading.Lock()
lock_b  = threading.Lock()
lock_c  = threading.Lock()
counter = 9

def worker(name, first, second):
    global counter
    while counter > 0:
        first.acquire()
        if not second.acquire(blocking=False):
            first.release()
            time.sleep(random()/10)
        else:
            try:
                if counter > 0:
                    counter -= 1
                    print(f"{name} took the turn, counter is {counter} now.")
            finally:
                second.release()
                first.release()

if __name__ == '__main__':
    threads = []
    threads.append(threading.Thread(target=worker, args=('Thread 1', lock_a, lock_b)))
    threads.append(threading.Thread(target=worker, args=('Thread 2', lock_b, lock_c)))
    threads.append(threading.Thread(target=worker, args=('Thread 3', lock_c, lock_a)))
    for thread in threads:
        thread.start()
    for thread in threads:
        thread.join()

NOTE: thread.start() starts executing the worker method in a separate thread. thread.join() makes the calling process to wait until the thread is done executing the worker.

Run the code as below:

$ py livelock.py
Thread 1 took the turn, counter is 8 now.
Thread 2 took the turn, counter is 7 now.
Thread 2 took the turn, counter is 6 now.
Thread 2 took the turn, counter is 5 now.
Thread 2 took the turn, counter is 4 now.
Thread 2 took the turn, counter is 3 now.
Thread 2 took the turn, counter is 2 now.
Thread 2 took the turn, counter is 1 now.
Thread 2 took the turn, counter is 0 now.
$

-- Back to Index --


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment