{"id":18001614,"url":"https://github.com/rigtorp/seqlock","last_synced_at":"2025-04-09T23:17:42.871Z","repository":{"id":137959122,"uuid":"48972008","full_name":"rigtorp/Seqlock","owner":"rigtorp","description":"An implementation of Seqlock in C++11","archived":false,"fork":false,"pushed_at":"2024-07-29T11:56:14.000Z","size":15,"stargazers_count":208,"open_issues_count":4,"forks_count":31,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-04-09T23:17:36.758Z","etag":null,"topics":["atomic","concurrency","cpp","cpp11","header-only","lock-free","no-dependencies"],"latest_commit_sha":null,"homepage":"","language":"C++","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/rigtorp.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-01-04T03:11:45.000Z","updated_at":"2025-04-03T00:07:29.000Z","dependencies_parsed_at":null,"dependency_job_id":"17354df8-8d26-4bfe-a2ed-741890d183d7","html_url":"https://github.com/rigtorp/Seqlock","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rigtorp%2FSeqlock","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rigtorp%2FSeqlock/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rigtorp%2FSeqlock/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rigtorp%2FSeqlock/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rigtorp","download_url":"https://codeload.github.com/rigtorp/Seqlock/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248125593,"owners_count":21051771,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["atomic","concurrency","cpp","cpp11","header-only","lock-free","no-dependencies"],"created_at":"2024-10-29T23:18:12.009Z","updated_at":"2025-04-09T23:17:42.847Z","avatar_url":"https://github.com/rigtorp.png","language":"C++","readme":"# Seqlock.h\n\n[![Build Status](https://travis-ci.org/rigtorp/Seqlock.svg?branch=master)](https://travis-ci.org/rigtorp/Seqlock)\n[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/rigtorp/Seqlock/master/LICENSE)\n\nImplementation of [seqlock](https://en.wikipedia.org/wiki/Seqlock) in\nC++11.\n\nA seqlock can be used as an alternative to\n[a readers-writer lock](https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock). It\nwill never block the writer and doesn't require any memory bus locks.\n\n## Example\n\n```cpp\nstruct Data {\n  std::size_t a, b, c;\n};\nSeqlock\u003cData\u003e sl;\nsl.store({0, 0, 0});\nauto t = std::thread([\u0026] {\n  for (;;) {\n    auto d = sl.load();\n    if (d.a + 100 == d.b \u0026\u0026 d.c == d.a + d.b) {\n      return;\n    }\n  }\n});\nsl.store({100, 200, 300});\nt.join();\n```\n\n## Usage\n\nCreate a seqlock:\n\n```cpp\nstruct Data {\n  std::size_t a, b, c;\n};\nSeqlock\u003cData\u003e sl;\n```\n\nStore a value (can only be called from a single thread):\n\n```cpp\nsl.store({1, 2, 3});\n```\n\nLoad a value (can be called from multiple threads):\n\n```cpp\nauto v = sl.load();\n```\n\n## Implementation\n\nImplementing the seqlock in portable C++11 is quite tricky. The basic\nseqlock implementation *unconditionally loads* the sequence number,\nthen *unconditionally loads* the protected data and finally\n*unconditionally loads* the sequence number again. Since loading the\nprotected data is done unconditionally on the sequence number the\ncompiler is free to move these loads before or after the loads from\nthe sequence number.\n\n```cpp\nT load() const noexcept {\n  T copy;\n  size_t seq0, seq1;\n  do {\n    seq0 = seq_.load(std::memory_order_acquire);\n    copy = value_;\n    seq1 = seq_.load(std::memory_order_acquire);\n  } while (seq0 != seq1 || seq0 \u0026 1);\n  return copy;\n}\n```\n\nCompiling this code specialized for `int` with `g++-5.2 -std=c++11 -O3` yields\nthe following assembly:\n\n```\n0000000000401ad0 \u003c_ZNK7rigtorp7SeqlockIiLm64EE4loadEv\u003e:\n  // copy = value_;\n  401ad0:\t8b 47 08             \tmov    0x8(%rdi),%eax\n  401ad3:\t0f 1f 44 00 00       \tnopl   0x0(%rax,%rax,1)\n  // do {\n  //   seq0 = seq_.load(std::memory_order_acquire);\n  401ad8:\t48 8b 0f             \tmov    (%rdi),%rcx\n  //   seq1 = seq_.load(std::memory_order_acquire);\n  401adb:\t48 8b 17             \tmov    (%rdi),%rdx\n  // } while (seq0 != seq1 || seq0 \u0026 1);\n  401ade:\t48 39 ca             \tcmp    %rcx,%rdx\n  401ae1:\t75 f5                \tjne    401ad8 \u003c_ZNK7rigtorp7SeqlockIiLm64EE4loadEv+0x8\u003e\n  401ae3:\t83 e2 01             \tand    $0x1,%edx\n  401ae6:\t75 f0                \tjne    401ad8 \u003c_ZNK7rigtorp7SeqlockIiLm64EE4loadEv+0x8\u003e\n  // return copy;\n  401ae8:\tf3 c3                \trepz retq \n  401aea:\t66 0f 1f 44 00 00    \tnopw   0x0(%rax,%rax,1)\n```\n\nWe see that the compiler did indeed reorder the load of the protected\ndata outside the critical section and the data is no longer protected\nfrom torn reads. Interestingly compiling using `clang++-3.7 -std=c++11\n-O3` produces the correct assembly:\n\n```\n0000000000401520 \u003c_ZNK7rigtorp7SeqlockIiLm64EE4loadEv\u003e:\n  // do {\n  //   seq0 = seq_.load(std::memory_order_acquire);  \n  401520:\t48 8b 0f             \tmov    (%rdi),%rcx\n  //   copy = value_;\n  401523:\t8b 47 08             \tmov    0x8(%rdi),%eax\n  //   seq1 = seq_.load(std::memory_order_acquire);\n  401526:\t48 8b 17             \tmov    (%rdi),%rdx\n  // } while (seq0 != seq1 || seq0 \u0026 1);\n  401529:\tf6 c1 01             \ttest   $0x1,%cl\n  40152c:\t75 f2                \tjne    401520 \u003c_ZNK7rigtorp7SeqlockIiLm64EE4loadEv\u003e\n  40152e:\t48 39 d1             \tcmp    %rdx,%rcx\n  401531:\t75 ed                \tjne    401520 \u003c_ZNK7rigtorp7SeqlockIiLm64EE4loadEv\u003e\n  // return copy;\n  401533:\tc3                   \tretq   \n  401534:\t66 2e 0f 1f 84 00 00 \tnopw   %cs:0x0(%rax,%rax,1)\n  40153b:\t00 00 00 \n  40153e:\t66 90                \txchg   %ax,%ax\n```\n\nThere are two ways to fix this:\n\n* Make the location of the protected data dependent on the sequence\n  number by storing multiple instances of the data and selecting which\n  one to read from based on the sequence number. This solution should\n  be portable to all CPU architectures, but requires extra space.\n* For x86 it's enough to insert a compiler barrier using\n  `std::atomic_signal_fence(std::memory_order_acq_rel)`. This will\n  only work on the [x86 memory model][x86-mm]. On\n  [ARM memory model][arm-mm] you need to inserts a `dmb` memory\n  barrier instruction, which is not possible in C++11.\n  \nSince my target architecture is x86 I've implemented the second\noption:\n\n```cpp\nT load() const noexcept {\n  T copy;\n  size_t seq0, seq1;\n  do {\n    seq0 = seq_.load(std::memory_order_acquire);\n    std::atomic_signal_fence(std::memory_order_acq_rel);\n    copy = value_;\n    std::atomic_signal_fence(std::memory_order_acq_rel);\n    seq1 = seq_.load(std::memory_order_acquire);\n  } while (seq0 != seq1 || seq0 \u0026 1);\n  return copy;\n}\n```\n\nCompiled with `g++-5.2 -std=c++11 -O3` it produces the\nfollowing correct assembly:\n\n```\n00000000004014e0 \u003c_ZNK7rigtorp7SeqlockIiE4loadEv\u003e:\n  4014e0:\t48 8d 4f 08          \tlea    0x8(%rdi),%rcx\n  4014e4:\t0f 1f 40 00          \tnopl   0x0(%rax)\n  // do {\n  //   seq0 = seq_.load(std::memory_order_acquire);\n  //   std::atomic_signal_fence(std::memory_order_acq_rel);\n  4014e8:\t48 8b 31             \tmov    (%rcx),%rsi\n  //   copy = value_;\n  4014eb:\t8b 07                \tmov    (%rdi),%eax\n  //   std::atomic_signal_fence(std::memory_order_acq_rel);\n  //   seq1 = seq_.load(std::memory_order_acquire);\n  4014ed:\t48 8b 11             \tmov    (%rcx),%rdx\n  // } while (seq0 != seq1 || seq0 \u0026 1);\n  4014f0:\t48 39 f2             \tcmp    %rsi,%rdx\n  4014f3:\t75 f3                \tjne    4014e8 \u003c_ZNK7rigtorp7SeqlockIiE4loadEv+0x8\u003e\n  4014f5:\t83 e2 01             \tand    $0x1,%edx\n  4014f8:\t75 ee                \tjne    4014e8 \u003c_ZNK7rigtorp7SeqlockIiE4loadEv+0x8\u003e\n  // return copy;\n  4014fa:\tf3 c3                \trepz retq \n  4014fc:\t0f 1f 40 00          \tnopl   0x0(%rax)\n```\n\nThe store operation is implemented in a similar manner to the load\noperation. Additionally the data and sequence counter is aligned and\npadded to prevent false sharing with adjacent data.\n\nReferences:\n\n* [fast reader/writer lock for gettimeofday 2.5.30](http://lwn.net/Articles/7388/)\n* [Can Seqlocks Get Along With Programming Language Memory Models?](http://www.hpl.hp.com/techreports/2012/HPL-2012-68.pdf)\n  ([slides](http://safari.ece.cmu.edu/MSPC2012/slides_posters/boehm-slides.pdf))\n* [x86-TSO: A Rigorous and Usable Programmer’s Model for x86 Multiprocessors][x86-mm]\n* [A Tutorial Introduction to the ARM and POWER Relaxed Memory Models][arm-mm]\n\n[x86-mm]: http://www.cl.cam.ac.uk/~pes20/weakmemory/cacm.pdf\n[arm-mm]: http://www.cl.cam.ac.uk/~pes20/ppc-supplemental/test7.pdf\n\n## Testing\n\nTesting lock-free algorithms is hard. I'm using two approaches to test\nthe implementation:\n\n* A test that `load()` and `store()` publish results in the correct\n  order on a single thread.\n* A multithreaded fuzz test that `load()` never see a partial\n  `store()` (torn read).\n\n## Potential improvements\n\nAllow partial updates and reads using a visitor pattern.\n\nSupport for multiple writers can be achieved by using a CAS loop to\nacquire the odd sequence number in the store operation. This would\nhave the same effect as wrapping the seqlock in a spinlock.\n\nTrade-off space for reduced readers-writer contention by striping\nwrites across multiple seqlocks.\n\n## About\n\nThis project was created by [Erik Rigtorp](http://rigtorp.se)\n\u003c[erik@rigtorp.se](mailto:erik@rigtorp.se)\u003e.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frigtorp%2Fseqlock","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frigtorp%2Fseqlock","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frigtorp%2Fseqlock/lists"}