{"id":13413107,"url":"https://github.com/ssgreg/stl","last_synced_at":"2026-01-14T23:46:41.742Z","repository":{"id":57496853,"uuid":"137878564","full_name":"ssgreg/stl","owner":"ssgreg","description":"Software Transactional Locks","archived":false,"fork":false,"pushed_at":"2020-07-24T08:20:52.000Z","size":17,"stargazers_count":30,"open_issues_count":0,"forks_count":5,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-07-31T20:51:55.528Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ssgreg.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-06-19T10:50:11.000Z","updated_at":"2024-05-31T23:59:56.000Z","dependencies_parsed_at":"2022-09-03T02:30:51.034Z","dependency_job_id":null,"html_url":"https://github.com/ssgreg/stl","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/ssgreg/stl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssgreg%2Fstl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssgreg%2Fstl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssgreg%2Fstl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssgreg%2Fstl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ssgreg","download_url":"https://codeload.github.com/ssgreg/stl/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssgreg%2Fstl/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28439537,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T22:37:52.437Z","status":"ssl_error","status_checked_at":"2026-01-14T22:37:31.496Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-30T20:01:33.546Z","updated_at":"2026-01-14T23:46:41.725Z","avatar_url":"https://github.com/ssgreg.png","language":"Go","readme":"# Software Transactional Locks\n\n![Alt text](https://user-images.githubusercontent.com/1574981/41672584-ef9140a4-74c2-11e8-9da2-9ca926f77a53.png \"(c) Ashley McNamara\")\n[![GoDoc](https://godoc.org/github.com/ssgreg/stl?status.svg)](https://godoc.org/github.com/ssgreg/stl)\n[![Build Status](https://travis-ci.org/ssgreg/stl.svg?branch=master)](https://travis-ci.org/ssgreg/stl)\n[![Go Report Status](https://goreportcard.com/badge/github.com/ssgreg/stl)](https://goreportcard.com/report/github.com/ssgreg/stl)\n[![Coverage Status](https://coveralls.io/repos/github/ssgreg/stl/badge.svg?branch=master)](https://coveralls.io/github/ssgreg/stl?branch=master)\n\nPackage `stl` provides multiple atomic dynamic shared/exclusive locks, based on [Software Transactional Memory](https://en.wikipedia.org/wiki/Software_transactional_memory) (STM) concurrency control mechanism.\nIn addition `stl` locks can take `context.Context` that allows to cancel or set a deadline for such locks.\n\nLocks are fast and lightweight. The implementation requires only one `mutex` and one `channel` per vault (a set of locks with any number of resources).\n\n## Installation\n\nInstall the package with:\n\n```shell\ngo get github.com/ssgreg/stl\n```\n\n## TL;DR\n\n`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).\n\nYou can also combine `shared` and `exclusive` resources while building a transaction or transactional locker:\n\n```go\n// A vault that holds all locked resources.\nv := stl.NewVault()\n\n// ...\n\nlocker := stl.New().Exclusive(\"terminal\").Shared(\"network\").ToLocker(v)\nlocker.Lock()\ndefer locker.Unlock()\n```\n\nIt'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.\n\n## Example: Dining Philosophers Problem\n\nIn computer science, the [dining philosophers problem](https://en.wikipedia.org/wiki/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:\n\nFive 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.\n\nThe 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.\n\nLet’s see how we can do it with `stl`.\n\nFirstly 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.\n\n```go\n// Two forks per each five philosophers.\nresources := [][]string{\n    {\"fork_1\", \"fork_2\"},\n    {\"fork_2\", \"fork_3\"},\n    {\"fork_3\", \"fork_4\"},\n    {\"fork_4\", \"fork_5\"},\n    {\"fork_5\", \"fork_1\"},\n}\n```\n\nLet'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).\n\nThis is how transaction lockers for each philosophers can be created.\n\n```go\n// A vault that holds all locked resources.\nv := stl.NewVault()\n\n// ...\n\nfor n := 0; n \u003c 5; n++ {\n    // ...\n    // A locker that can exclusively lock/unlock both forks atomically.\n    locker := stl.New().Exclusive(resources[n][0]).Exclusive(resources[n][1]).ToLocker(v)\n    // ...\n}\n```\n\nTo 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.\n\n```go\n// Philosopher is thinking here...\n// ...\n\n// Now he decided to take forks and eat a little bit of his spaghetti.\nlocker.Lock()\ndefer locker.Unlock()\n\n// Philosopher is eating here...\n// ...\n```\n\nBut 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.\n\nWe are about to finish our solution. Think-eat function:\n\n```go\ndo := func(locker Locker) {\n    // Think for 300 ms.\n    time.Sleep(time.Millisecond * 300)\n\n    // Wait for free forks.\n    locker.Lock()\n    defer locker.Unlock()\n\n    // Eat for 100ms.\n    time.Sleep(time.Millisecond * 100)\n}\n```\n\nEvaluation:\n\n```go\nfor n := 0; n \u003c 5; n++ {\n    // Each of five philosopher live in it's own goroutine.\n    go func(n int) {\n        // A locker that can exclusively lock/unlock both forks atomically.\n        locker := New().Exclusive(resources[n][0]).Exclusive(resources[n][1]).ToLocker(v)\n\n        // Think-Eat for five times.\n        for i := 0; i \u003c 5; i++ {\n            do(locker)\n        }\n    }(n)\n}\n```\n\nIf you run the code, you’ll see the following output (with my comments):\n\n```\n19.663µs        | Fifth  is thinking 1th time\n57.726µs        | Second is thinking 1th time\n131.391µs       | First  is thinking 1th time\n98.969µs        | Third  is thinking 1th time\n206.433µs       | Fourth is thinking 1th time\n```\n\n\u003e All five philosophers are starting to think.\n\n```\n304.330265ms    | Fourth is eating 1th time, was starving for 102.93µs\n304.304333ms    | First  is eating 1th time, was starving for 90.307µs\n```\n\n\u003e 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.\n\n```\n404.955441ms    | Third  is eating 1th time, was starving for 100.733725ms\n404.952165ms    | Fourth is thinking 2th time\n404.986976ms    | First  is thinking 2th time\n405.005764ms    | Fifth  is eating 1th time, was starving for 100.758988ms\n```\n\n\u003e 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.\n\n```\n505.144439ms    | Third  is thinking 2th time\n505.153147ms    | Fifth  is thinking 2th time\n505.195732ms    | Second is eating 1th time, was starving for 200.986826ms\n```\n\n\u003e 100ms later. Third and Fifth finished their meal. Second is the only dining philosopher now. He was starving for 200ms while waiting for others.\n\n```\n608.972697ms    | Second is thinking 2th time\n705.095973ms    | Fourth is eating 2th time, was starving for 8.551µs\n705.104647ms    | First  is eating 2th time, was starving for 12.506µs\n808.134488ms    | First  is thinking 3th time\n808.187194ms    | Fourth is thinking 3th time\n808.194107ms    | Third  is eating 2th time, was starving for 110.638µs\n808.228277ms    | Fifth  is eating 2th time, was starving for 131.242µs\n913.369796ms    | Fifth  is thinking 3th time\n913.417331ms    | Third  is thinking 3th time\n913.433097ms    | Second is eating 2th time, was starving for 73.613µs\n1.016172052s    | Second is thinking 3th time\n1.113263947s    | Fourth is eating 3th time, was starving for 6.818µs\n1.113268306s    | First  is eating 3th time, was starving for 10.244µs\n1.216127462s    | First  is thinking 4th time\n1.216138289s    | Fourth is thinking 4th time\n1.216149318s    | Third  is eating 3th time, was starving for 23.142µs\n1.216168988s    | Fifth  is eating 3th time, was starving for 57.036µs\n1.31888003s     | Fifth  is thinking 4th time\n1.318897601s    | Third  is thinking 4th time\n1.318909899s    | Second is eating 3th time, was starving for 87.83µs\n1.421858869s    | Second is thinking 4th time\n1.518619148s    | Fourth is eating 4th time, was starving for 8.182µs\n1.518632238s    | First  is eating 4th time, was starving for 6.313µs\n1.622124278s    | Fourth is thinking 5th time\n1.622126414s    | First  is thinking 5th time\n1.622132975s    | Fifth  is eating 4th time, was starving for 15.59µs\n1.62213733s     | Third  is eating 4th time, was starving for 27.384µs\n1.725392718s    | Fifth  is thinking 5th time\n1.725411187s    | Third  is thinking 5th time\n1.725421148s    | Second is eating 4th time, was starving for 17.534µs\n1.829165491s    | Second is thinking 5th time\n1.926475691s    | Fourth is eating 5th time, was starving for 10.229µs\n1.926481498s    | First  is eating 5th time, was starving for 6.972µs\n2.028900949s    | Third  is eating 5th time, was starving for 39.312µs\n2.028929715s    | Fifth  is eating 5th time, was starving for 64.779µs\n2.13402499s     | Second is eating 5th time, was starving for 6.024µs\n```\n\n\u003e No philosophers were waiting for others. Their found they think-eat balance.\n\n## Conclusion\n\n`stl` provides you some handy way to build reliable deadlock-free applications.\n\n## Additional materials\n\n* [Software Transactional Memory (Wikipedia)](https://en.wikipedia.org/wiki/Software_transactional_memory)\n* [Dining Philosophers Problem (Wikipedia)](https://en.wikipedia.org/wiki/Dining_philosophers_problem)\n* [TS 19841:2015 Transactional Memory](https://www.iso.org/standard/66343.html)\n* [Beyound locks: Software Transactional Memory](https://bartoszmilewski.com/2010/09/11/beyond-locks-software-transactional-memory/)\n","funding_links":[],"categories":["Goroutines","Goroutines `goroutines的管理和使用`","Relational Databases"],"sub_categories":["Search and Analytic Databases","检索及分析资料库","Advanced Console UIs","SQL 查询语句构建库"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fssgreg%2Fstl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fssgreg%2Fstl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fssgreg%2Fstl/lists"}