Software Transactional Locks

Software Transactional Locks

Alt text GoDoc Build Status Go Report Status Coverage Status

Package stl provides multiple atomic dynamic shared/exclusive locks, based on Software Transactional Memory (STM) concurrency control mechanism. In addition stl locks can take context.Context that allows to cancel or set a deadline for such locks.

Locks are fast and lightweight. The implementation requires only one mutex and one channel per vault (a set of locks with any number of resources).

Installation

Install the package with:

go get github.com/ssgreg/stl

TL;DR

stl can lock any number of resources atomically without a deadlock. Each resource can be locked in exclusive manner (only one locker can lock such resource at the same time) or in shared manner (all 'shared' lockers can lock such resource at the same time, a locker that wants to lock such resource 'exclusively' will wait them to finish).

You can also combine shared and exclusive resources while building a transaction or transactional locker:

// A vault that holds all locked resources.
v := stl.NewVault()

// ...

locker := stl.New().Exclusive("terminal").Shared("network").ToLocker(v)
locker.Lock()
defer locker.Unlock()

It's also possible to call locker.LockWithContext(ctx) if you want to be able to cancel or set a deadline for locking operation. This will add additional flexibility to your applications.

Example: Dining Philosophers Problem

In computer science, the dining philosophers problem is an example problem often used in concurrent algorithm design to illustrate synchronization issues and techniques for resolving them. Notably, with stl, the task can be solved elegantly comparing to any other “bare” solutions. The following is a short description of the problem:

Five philosophers are sitting at the circle dining table. There are five plates with spaghetti on it, and also five forks arranged so that every philosopher can take two nearest forks in both his hands. Philosophers either can think about their philosophical problems, or they can eat spaghetti. The thinkers are obligated to use two forks for eating. If a philosopher holds just one fork, he can’t eat or think. It's needed to organize their existence so that every philosopher could eat and think by turn, forever.

The task doesn’t sound that difficult, right? But aware of some pitfalls unseen from the first sight. The root of the problem are forks which can be considered as shared mutable resources for goroutines (philosophers). Any two neighbor thinkers are competing for the fork between them, and this enables such silly situations like “every philosopher has took the right fork, and they all stuck because no one could take the left fork anymore”. It's a deadlock. Thread starvation problem also can occur in a wrongly developed code, and, ironically, it will result in “starvation” of some philosophers: while part of them eat and think normally, other part can acquire resources hardly ever. So in good solutions all philosophers should pass their think-eat iterations almost equally.

Let’s see how we can do it with stl.

Firstly we need to represent forks (resources) in our concurrent model. To distinguish different forks, we’ll assign some label to each. There is no need to create and keep resources itself using stl. Labels (or names) are enoughs.

// Two forks per each five philosophers.
resources := [][]string{
    {"fork_1", "fork_2"},
    {"fork_2", "fork_3"},
    {"fork_3", "fork_4"},
    {"fork_4", "fork_5"},
    {"fork_5", "fork_1"},
}

Let's continue with constructing a transaction (or a transaction locker like in this case). When the philosopher changes his activity from thinking to eating, he tries to take the left and the right fork exclusively. If he successfully takes (locks) both, he spends some time eating his spaghetti. If any of the forks is taken by a neighbor, our philosopher should wait for any other philosopher to finish eating (unlock).

This is how transaction lockers for each philosophers can be created.

// A vault that holds all locked resources.
v := stl.NewVault()

// ...

for n := 0; n < 5; n++ {
    // ...
    // A locker that can exclusively lock/unlock both forks atomically.
    locker := stl.New().Exclusive(resources[n][0]).Exclusive(resources[n][1]).ToLocker(v)
    // ...
}

To change philosopher's activity (exclusively lock both specified resources) we need to call locker.Lock() and locker.Unlock() in the end. It's also possible to call locker.LockWithContext(ctx) if we want to be able to cancel or set a deadline for locking operation because it could take some time waiting for other actors to unlock used resources.

// Philosopher is thinking here...
// ...

// Now he decided to take forks and eat a little bit of his spaghetti.
locker.Lock()
defer locker.Unlock()

// Philosopher is eating here...
// ...

But now we have to stop and understand what this transaction locker will do. Suppose, both forks were free, then the locker will successfully lock both resources their represent. However it’s more likely one of the forks was already taken by someone else. “All or nothing”, - this principle works well with STM mechanism. If any fork of the two was already taken, the transaction will be restarted until the locker will successfully lock both resources. We can consider the transaction will be successful if and only if all locker's resources will be successfully locked. In other words the transaction locker won’t proceed further if some of the forks was in the undesired state.

We are about to finish our solution. Think-eat function:

do := func(locker Locker) {
    // Think for 300 ms.
    time.Sleep(time.Millisecond * 300)

    // Wait for free forks.
    locker.Lock()
    defer locker.Unlock()

    // Eat for 100ms.
    time.Sleep(time.Millisecond * 100)
}

Evaluation:

for n := 0; n < 5; n++ {
    // Each of five philosopher live in it's own goroutine.
    go func(n int) {
        // A locker that can exclusively lock/unlock both forks atomically.
        locker := New().Exclusive(resources[n][0]).Exclusive(resources[n][1]).ToLocker(v)

        // Think-Eat for five times.
        for i := 0; i < 5; i++ {
            do(locker)
        }
    }(n)
}

If you run the code, you’ll see the following output (with my comments):

19.663µs        | Fifth  is thinking 1th time
57.726µs        | Second is thinking 1th time
131.391µs       | First  is thinking 1th time
98.969µs        | Third  is thinking 1th time
206.433µs       | Fourth is thinking 1th time

All five philosophers are starting to think.

304.330265ms    | Fourth is eating 1th time, was starving for 102.93µs
304.304333ms    | First  is eating 1th time, was starving for 90.307µs

300ms later. Fourth and First are eating now. They took forks with numbers 1, 2, 4, 5. Second and Third can't eat with one fork 3. Fifth can't even take a single fork.

404.955441ms    | Third  is eating 1th time, was starving for 100.733725ms
404.952165ms    | Fourth is thinking 2th time
404.986976ms    | First  is thinking 2th time
405.005764ms    | Fifth  is eating 1th time, was starving for 100.758988ms

100ms later. Fourth and First finished eating. They put their forks. Forks with number 3, 4, 5, 1 was taken by Third and Fifth. Second is really unlucky guy.

505.144439ms    | Third  is thinking 2th time
505.153147ms    | Fifth  is thinking 2th time
505.195732ms    | Second is eating 1th time, was starving for 200.986826ms

100ms later. Third and Fifth finished their meal. Second is the only dining philosopher now. He was starving for 200ms while waiting for others.

608.972697ms    | Second is thinking 2th time
705.095973ms    | Fourth is eating 2th time, was starving for 8.551µs
705.104647ms    | First  is eating 2th time, was starving for 12.506µs
808.134488ms    | First  is thinking 3th time
808.187194ms    | Fourth is thinking 3th time
808.194107ms    | Third  is eating 2th time, was starving for 110.638µs
808.228277ms    | Fifth  is eating 2th time, was starving for 131.242µs
913.369796ms    | Fifth  is thinking 3th time
913.417331ms    | Third  is thinking 3th time
913.433097ms    | Second is eating 2th time, was starving for 73.613µs
1.016172052s    | Second is thinking 3th time
1.113263947s    | Fourth is eating 3th time, was starving for 6.818µs
1.113268306s    | First  is eating 3th time, was starving for 10.244µs
1.216127462s    | First  is thinking 4th time
1.216138289s    | Fourth is thinking 4th time
1.216149318s    | Third  is eating 3th time, was starving for 23.142µs
1.216168988s    | Fifth  is eating 3th time, was starving for 57.036µs
1.31888003s     | Fifth  is thinking 4th time
1.318897601s    | Third  is thinking 4th time
1.318909899s    | Second is eating 3th time, was starving for 87.83µs
1.421858869s    | Second is thinking 4th time
1.518619148s    | Fourth is eating 4th time, was starving for 8.182µs
1.518632238s    | First  is eating 4th time, was starving for 6.313µs
1.622124278s    | Fourth is thinking 5th time
1.622126414s    | First  is thinking 5th time
1.622132975s    | Fifth  is eating 4th time, was starving for 15.59µs
1.62213733s     | Third  is eating 4th time, was starving for 27.384µs
1.725392718s    | Fifth  is thinking 5th time
1.725411187s    | Third  is thinking 5th time
1.725421148s    | Second is eating 4th time, was starving for 17.534µs
1.829165491s    | Second is thinking 5th time
1.926475691s    | Fourth is eating 5th time, was starving for 10.229µs
1.926481498s    | First  is eating 5th time, was starving for 6.972µs
2.028900949s    | Third  is eating 5th time, was starving for 39.312µs
2.028929715s    | Fifth  is eating 5th time, was starving for 64.779µs
2.13402499s     | Second is eating 5th time, was starving for 6.024µs

No philosophers were waiting for others. Their found they think-eat balance.

Conclusion

stl provides you some handy way to build reliable deadlock-free applications.

Additional materials

Owner
Grigory Zubankov
Unit Manager @Acronis Go, C++, C
Grigory Zubankov
Similar Resources

Secure software enclave for storage of sensitive information in memory.

MemGuard Software enclave for storage of sensitive information in memory. This package attempts to reduce the likelihood of sensitive data being expos

Dec 30, 2022

A codename generator meant for naming software releases.

codename-generator This library written in Golang generates a random code name meant for naming software releases if you run short of inspiration. Cur

Jun 26, 2022

Golang (Go) bindings for GNU's gettext (http://www.gnu.org/software/gettext/)

gosexy/gettext Go bindings for GNU gettext, an internationalization and localization library for writing multilingual systems. Requeriments GNU gettex

Nov 16, 2022

A client software for acme-dns with emphasis on usability and guidance through setup and additional security safeguard mechanisms

acme-dns-client A client software for acme-dns with emphasis on usability and guidance through setup and additional security safeguard mechanisms. It

Dec 2, 2022

oDrop, a fast efficient cross-platform file transfer software for server and home environments

oDrop is a cross-platform LAN file transfer software to efficiently transfer files between computers, oDrop is useful in environments where GUI is not available.

Jun 4, 2022

F' - A flight software and embedded systems framework

F´ (F Prime) is a component-driven framework that enables rapid development and deployment of spaceflight and other embedded software applications.

Jan 4, 2023

Get cloud instances with your favourite software pre-loaded

This Golang package can be used to provision cloud hosts using a simple CRUD-style API along with a cloud-init user-data script. It could be used to automate anything from k3s clusters, to blogs, or CI runners. We use it to create the cheapest possible hosts in the cloud with a public IP address.

Dec 14, 2022

Continuous performance analysis reports for software projects 🤖

Performabot Welcome to Performabot! This little helper can be used to provide Continuous Performance Reports within your GitHub project. But please be

Nov 16, 2022

A small utility that aims to automate and simplify some tasks related to software release cycles.

Stork is a small utility that aims to automate and simplify some tasks related to software release cycles such as reading the current version from a f

Nov 9, 2022

BitMaelum software suite

Image if we could redesign email without thinking about backward compatibility. What would it look like? Could we solve the problems we face nowadays

Nov 15, 2022

Path to a Software Architect

Path to a Software Architect

Contents What is a Software Architect? Levels of Architecture Typical Activities Important Skills (1) Design (2) Decide (3) Simplify (4) Code (5) Docu

Dec 28, 2022

Pholcus is a distributed high-concurrency crawler software written in pure golang

Pholcus is a distributed high-concurrency crawler software written in pure golang

Pholcus Pholcus(幽灵蛛)是一款纯 Go 语言编写的支持分布式的高并发爬虫软件,仅用于编程学习与研究。 它支持单机、服务端、客户端三种运行模式,拥有Web、GUI、命令行三种操作界面;规则简单灵活、批量任务并发、输出方式丰富(mysql/mongodb/kafka/csv/excel等

Dec 30, 2022

Simple, fast, safe, compiled language for developing maintainable software. Compiles itself in 1s with zero library dependencies. https://vlang.io

Simple, fast, safe, compiled language for developing maintainable software. Compiles itself in <1s with zero library dependencies. https://vlang.io

The V Programming Language vlang.io | Docs | Changelog | Speed | Contributing & compiler design Key Features of V Simplicity: the language can be lear

Jan 4, 2023

Canvas is a Go drawing library based on OpenGL or using software rendering that is very similar to the HTML5 canvas API

Canvas is a Go drawing library based on OpenGL or using software rendering that is very similar to the HTML5 canvas API

Go canvas Canvas is a pure Go library that provides drawing functionality as similar as possible to the HTML5 canvas API. It has nothing to do with HT

Jan 3, 2023

Software modular synthesizer with Midi support

go-modular A software modular synthesizer with Midi support. !! Warning: High amplitude sounds can cause serious ear damage. Lower your headphone volu

Mar 31, 2022

Software for archiving my digital stuff like tweets

rsms's memex Software for managing my digital information, like tweets. Usage First check out the source and build. You'll need Make and Go installed.

Nov 17, 2022

Hermit manages isolated, self-bootstrapping sets of tools in software projects.

Hermit - uniform tooling for Linux and Mac Hermit installs tools for software projects in self-contained, isolated sets, so your team, your contributo

Jan 3, 2023

Software of development with Golang

Software of development with Golang

Go-Simple-Rest-Api Description This repository is a Software of Development with Go,Mux,etc Installation Using Go 1.16.3 Server preferably. DataBase U

Dec 13, 2021

Software of Development with Golang and MySQL

Software of Development with Golang and MySQL

CRUD REST API GOLANG GORM AND MYSQL Description This repository is a Software of Application with Golang, Mux, GORM (ORM) and MySQL. Installation Usin

Nov 24, 2022
Related tags
Gue is Golang queue on top of PostgreSQL that uses transaction-level locks.

Gue is Golang queue on top of PostgreSQL that uses transaction-level locks.

Jan 4, 2023
Flock is a project which provides a Go solution for system level file locks for all platforms Golang supports.

Flock is a project which provides a Go solution for system level file locks for all platforms Golang supports.

Feb 8, 2022
Golang package that generates clean, responsive HTML e-mails for sending transactional mail
Golang package that generates clean, responsive HTML e-mails for sending transactional mail

Hermes Hermes is the Go port of the great mailgen engine for Node.js. Check their work, it's awesome! It's a package that generates clean, responsive

Dec 28, 2022
A lightweight transactional message bus on top of RabbitMQ

grabbit A lightweight transactional message bus on top of RabbitMQ supporting: Supported Messaging Styles One Way (Fire and forget) Publish/Subscribe

Dec 22, 2022
Replacement of ApacheBench(ab), support for transactional requests, support for command line and package references to HTTP stress testing tool.

stress stress is an HTTP stress testing tool. Through this tool, you can do a stress test on the HTTP service and get detailed test results. It is ins

Aug 23, 2022
A demonstration of the transactional outbox messaging pattern (+ Log Trailing) with Amazon DynamoDB (+ Streams) written in Go.
A demonstration of the transactional outbox messaging pattern (+ Log Trailing) with Amazon DynamoDB (+ Streams) written in Go.

Transactional Outbox Pattern in Amazon DynamoDB A demonstration of the transactional outbox messaging pattern (+ Log Trailing) with Amazon DynamoDB (+

Apr 12, 2022
Apr 12, 2022
Tidb - An open-source NewSQL database that supports Hybrid Transactional and Analytical Processing (HTAP) workloads
Tidb - An open-source NewSQL database that supports Hybrid Transactional and Analytical Processing (HTAP) workloads

What is TiDB? TiDB ("Ti" stands for Titanium) is an open-source NewSQL database

Jan 5, 2022
Conception was an experimental project, looking for ways to make software development more efficient.
Conception was an experimental project, looking for ways to make software development more efficient.

Conception Note: All future development is done in the Go version. Conception is an experimental research project, meant to become a modern IDE/Langua

Jul 21, 2022
Uniqush is a free and open source software system which provides a unified push service for server side notification to apps on mobile devices.

Homepage Download Blog/News @uniqush Introduction Uniqush (\ˈyü-nə-ku̇sh\ "uni" pronounced as in "unified", and "qush" pronounced as in "cushion") is

Jan 9, 2023