Skip to content

Commit

Permalink
Update README
Browse files Browse the repository at this point in the history
  • Loading branch information
farhadi committed Jan 13, 2024
1 parent 144f17c commit 21912d7
Showing 1 changed file with 53 additions and 0 deletions.
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,56 @@
[![License](http://img.shields.io/hexpm/l/spinlock.svg?style=flat)](https://github.com/farhadi/spinlock/blob/master/LICENSE)

Spinlock implemented using [atomics](https://erlang.org/doc/man/atomics.html) for Erlang and Elixir.

## Introduction

In software engineering, locks and mutexes are usually used to prevent race conditions and
data corruption that can occur when multiple threads try to access shared data concurrently.
However, in Erlang, we rarely need such locking mechanisms. This is due to the fact that
concurrency in Erlang is based on actor model, and each process has its own separate heap
and does not share memory with other processes. This model inherently avoids many of the
concurrency issues associated with shared memory and locking mechanisms.

In erlang shared resources are typically managed through serialization of access requests
using a single gen_server process to handle all requests for a resource. It's also possible
to replicate mutex behavior using a gen_server process (see [sleeplocks](https://github.com/whitfin/sleeplocks)
as an example of a lock implementation using gen_servers). Generally, the performance of
such locks are good enough for most use cases. However, in some critical situations where
submicrosecond latency is a requirement, relying on a gen_server based lock may not be the
best choice. In such critical cases Spinlocks are used.

Spinlocks are a low-level synchronization mechanism typically used in systems programming,
such as in operating system kernels or in scenarios where you need very fast, lightweight locks.
They are called "spinlocks" because they cause a thread attempting to acquire the lock to "spin"
in a loop while repeatedly checking if the lock is available. Using this technique we can
achieve extremely fast lock acquisitions at the expense of CPU cycles spent in busy-waiting.

## Implementation

This is a Spinlock implementation using atomics for Erlang and Elixir. It is slightly different
from other typical spinlock implementations where the lock state alternates between locked and
unlocked via an atomic operation. Here, a lock consists of two atomic integers: one tracks
the number of requests to acquire the lock, and the other holds the number of releases.

This approach offers several advantages over conventional spinlock implementations. Firstly,
lock acquisition is ordered, meaning locks are acquired in the same sequence they are requested.
This feature ensures consistent and predictable latency. Secondly, it tracks the number of
processes concurrently busy-waiting to acquire a lock. By allowing only the initial few processes
to busy-wait in a tight loop and interrupting spinning for the others with `erlang:yield/0`,
this method reduces spinlock starvation and offers other processes more opportunities to run.

In this implementation, attempting to acquire a lock in a locked state will forcibly release
the lock after a configurable number of attempts, under the assumption that the process owning
the lock has already terminated.

## Use Cases

Use it with caution due to its busy-waiting nature. If the lock is held for extended periods,
it could waste CPU cycles, adversely affecting overall system performance.

The rule of thumb to decide whether to use it or not is: "Employ it only if the operation
requiring the lock is a submicrosecond task, and if there's more than a 99% chance that the
lock can be acquired without busy-waiting."

A typical use case in Erlang is implementing transactions for data structures built on top of
atomic arrays (e.g. [Cuckoo Filter](http://github.com/farhadi/cuckoo_filter)).

0 comments on commit 21912d7

Please sign in to comment.