{"id":13608040,"url":"https://github.com/jonhoo/drwmutex","last_synced_at":"2025-04-06T11:08:20.905Z","repository":{"id":31675059,"uuid":"35240578","full_name":"jonhoo/drwmutex","owner":"jonhoo","description":"Distributed RWMutex in Go","archived":false,"fork":false,"pushed_at":"2019-05-19T18:30:33.000Z","size":483,"stargazers_count":350,"open_issues_count":6,"forks_count":17,"subscribers_count":15,"default_branch":"master","last_synced_at":"2025-03-30T09:09:40.706Z","etag":null,"topics":["golang","locking","multi-core","scalability","synchronization"],"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/jonhoo.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":"2015-05-07T19:57:09.000Z","updated_at":"2025-03-14T11:43:25.000Z","dependencies_parsed_at":"2022-08-26T12:23:53.639Z","dependency_job_id":null,"html_url":"https://github.com/jonhoo/drwmutex","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/jonhoo%2Fdrwmutex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonhoo%2Fdrwmutex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonhoo%2Fdrwmutex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonhoo%2Fdrwmutex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jonhoo","download_url":"https://codeload.github.com/jonhoo/drwmutex/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247471520,"owners_count":20944158,"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":["golang","locking","multi-core","scalability","synchronization"],"created_at":"2024-08-01T19:01:23.803Z","updated_at":"2025-04-06T11:08:20.879Z","avatar_url":"https://github.com/jonhoo.png","language":"Go","readme":"# Distributed Read-Write Mutex in Go\n\nThe default Go implementation of\n[sync.RWMutex](https://golang.org/pkg/sync/#RWMutex) does not scale well\nto multiple cores, as all readers contend on the same memory location\nwhen they all try to atomically increment it. This repository provides\nan `n`-way RWMutex, also known as a \"big reader\" lock, which gives each\nCPU core its own RWMutex. Readers take only a read lock local to their\ncore, whereas writers must take all locks in order.\n\n**Note that the current implementation only supports x86 processors on\nLinux; other combinations will revert (automatically) to the old\nsync.RWMutex behaviour. To support other architectures and OSes, the\nappropriate `cpu_GOARCH.go` and `cpus_GOOS.go` files need to be written.\nIf you have a different setup available, and have the time to write one\nof these, I'll happily accept patches.**\n\n## Finding the current CPU\n\nTo determine which lock to take, readers use the CPUID instruction,\nwhich gives the APICID of the currently active CPU without having to\nissue a system call or modify the runtime. This instruction is supported\non both Intel and AMD processors; ARM CPUs should use the [CPU ID\nregister](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0360e/CACEDHJG.html)\ninstead. For systems with more than 256 processors, x2APIC must be used,\nand the EDX register after CPUID with EAX=0xb should be used instead. A\nmapping from APICID to CPU index is constructed (using CPU affinity\nsyscalls) when the program is started, as it is static for the lifetime\nof a process.  Since the CPUID instruction can be fairly expensive,\ngoroutines will also only periodically update their estimate of what\ncore they are running on.  More frequent updates lead to less inter-core\nlock traffic, but also increases the time spent on CPUID instructions\nrelative to the actual locking.\n\n**Stale CPU information.**\nThe information of which CPU a goroutine is running on *might* be stale\nwhen we take the lock (the goroutine could have been moved to another\ncore), but this will only affect performance, not correctness, as long\nas the reader remembers which lock it took. Such moves are also\nunlikely, as the OS kernel tries to keep threads on the same core to\nimprove cache hits.\n\n## Performance\n\nThere are many parameters that affect the performance characteristics of\nthis scheme. In particular, the frequency of CPUID checking, the number\nof readers, the ratio of readers to writers, and the time readers hold\ntheir locks, are all important. Since only a single writer is active at\nthe time, the duration a writer holds a lock for does not affect the\ndifference in performance between sync.RWMutex and DRWMutex.\n\nExperiments show that DRWMutex performs better the more cores the system\nhas, and in particular when the fraction of writers is \u003c1%, and CPUID is\ncalled at most every 10 locks (this changes depending on the duration a\nlock is held for). Even on few cores, DRWMutex outperforms sync.RWMutex\nunder these conditions, which are common for applications that elect to\nuse sync.RWMutex over sync.Mutex.\n\nThe plot below shows mean performance across 30 runs (using\n[experiment](https://github.com/jonhoo/experiment)) as the number of\ncores increases using:\n\n    drwmutex-bench -i 5000 -p 0.0001 -n 500 -w 1 -r 100 -c 100\n\n![DRWMutex and sync.RWMutex performance comparison](benchmarks/perf.png)\n\nError bars denote 25th and 75th percentile.\nNote the drops every 10th core; this is because 10 cores constitute a\nNUMA node on the machine the benchmarks were run on, so once a NUMA node\nis added, cross-core traffic becomes more expensive. Performance\nincreases for DRWMutex as more readers can work in parallel compared to\nsync.RWMutex.\n\nSee the [go-nuts\nthread](https://groups.google.com/d/msg/golang-nuts/zt_CQssHw4M/TteNG44geaEJ)\nfor further discussion.\n","funding_links":[],"categories":["Go","Concurrency"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonhoo%2Fdrwmutex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjonhoo%2Fdrwmutex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonhoo%2Fdrwmutex/lists"}