gofrs/flock, a small file lock for Go programs that need one process at a time
Thread-safe file locking library in Go
At a glance
- What is it?
- gofrs/flock is a BSD-3-Clause library that adds advisory file locking to Go, with a blocking Lock and a non-blocking TryLock, and it handles the cross-platform differences between flock and fcntl underneath. It is a single file per platform plus a shared core, and it has no dependencies beyond the standard library and golang.org/x/sys.
- Who is it for?
- Adopt gofrs/flock if you need a mutex that survives a process crash or coordinates between separate programs, since an in-process sync.Mutex does neither. Skip it if you only need to serialise goroutines inside one process, because a channel or a mutex is simpler and faster.
- Can I use it commercially?
- Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 22 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the library does and why you would reach for it
gofrs/flock implements a thread-safe file lock, and it also includes a non-blocking TryLock function to allow locking without blocking execution.
The distinction it draws is between two different problems that both get called locking. A Go program that needs one goroutine at a time to touch a resource can use sync.Mutex, and needs nothing else. What flock provides is a lock that is visible to other processes, is released by the operating system when the process dies, and survives a crash.
That last property is the one people usually care about. A mutex held by a goroutine that panics without releasing it leaves the program stuck. A file lock held by a process that is killed is released by the kernel. So the classic use is a scheduled job or a migration that must not run twice at once, where the second instance should wait or exit rather than corrupt state that the first one is halfway through writing.
The other classic use is coordination between different programs that were not written together, where a lock file in a shared directory is the only available handshake.
What it is not is a database lock, a distributed lock service, or a replacement for a transaction. It is an advisory lock on a path, which means it only coordinates processes that all agree to use it. Nothing stops another program from writing the same file.
Installation is the standard go get:
go get -u github.com/gofrs/flockThe dependency list in go.mod is short: stretchr/testify for the tests and golang.org/x/sys for the system calls, with go.yaml.in/yaml/v3 coming in indirectly through the test dependency. The module declares Go 1.26.0, so it tracks current Go releases rather than supporting an old one.
The usage pattern and what the return value means
The README's usage example is short and shows the shape of the API.
import "github.com/gofrs/flock"
fileLock := flock.New("/var/lock/go-lock.lock")
locked, err := fileLock.TryLock()
if err != nil {
// handle locking error
}
if locked {
// do work
fileLock.Unlock()
}Three things about that example are worth noticing.
The lock object is created from a path, and New is not an error-returning call. Nothing touches the filesystem at that point; the file is created or opened when the lock is attempted. That is why creating a lock object for a path that does not exist yet is fine.
TryLock returns two values, a boolean and an error, and they mean different things. The error is a genuine failure such as a permission problem or a bad path. The boolean is the ordinary outcome that another holder has the lock, which is not an error at all. That distinction is why the example checks err first and then tests locked, and why conflating the two is the classic mistake with this API.
And the release call sits inside the success branch, not in a defer. The example is minimal rather than exemplary on that point, because a defer immediately after a successful lock is the safer habit in real code, since it releases the lock even if the work panics.
The API documentation lives on pkg.go.dev, which the README links as the place for more detailed usage information. The package path is the same URL as the module path, so the godoc page is the reference for the methods the README does not show, including the blocking Lock and the context-aware variants.
How the cross-platform locking is structured
The repository layout shows the implementation split, and it is a good way to understand what the library does.
flock.go holds the shared logic: the lock object, its state, and the parts that are the same everywhere. Then there are the platform files. flock_unix.go covers the Unix side, and flock_unix_fcntl.go is the variant that uses fcntl locking instead of flock. flock_windows.go covers Windows, and flock_others.go is the fallback for platforms with neither.
That two-file Unix split is the interesting part. POSIX has two different advisory locking mechanisms, flock and fcntl, and they behave differently in ways that matter. flock is associated with an open file description, so a lock held through one file descriptor is released when the last descriptor referring to that description is closed. fcntl locks are associated with the process, and closing any descriptor to the file releases all of them.
The difference bites when a Go program opens a file more than once, or when a library in the same process opens the same path. With the wrong mechanism, closing one handle silently drops the lock. Choosing between them is the kind of decision that has to be made per platform and per use, and having it in the library rather than in your application is the point.
The Windows implementation differs for the obvious reason, since the underlying API is different in both shape and semantics, and flock_others.go exists so the package still compiles on a platform with neither mechanism.
The test files follow the same structure: flock_test.go for the general behaviour, flock_internal_test.go for the internals, and flock_example_test.go for the documented examples, which is the Go convention that keeps the examples in the documentation honest.
Choosing a lock path and the mistakes that show up in practice
Two operational details decide whether a file lock behaves the way you expect, and neither is in the API.
The first is the lock path itself. It has to be somewhere every participant can reach and somewhere the operating system will not clean up on its own. A path inside the application's working directory is fragile, because the working directory differs between a shell, a service manager and a cron job. A path under a fixed system directory such as the example in the README, /var/lock/go-lock.lock, is the conventional answer on Unix. The file does not need to exist beforehand, since nothing touches the filesystem until a lock is attempted.
The second is that advisory locks only work if everyone cooperates. The lock is advisory in the strict sense: it does not stop another program from writing the file, it only tells programs that check it. If a second tool writes to the same file without taking the lock, nothing happens and you have a corrupted file rather than a failed lock attempt.
Filesystem support is the third variable. Advisory locking is implemented by the filesystem, and network filesystems often implement it differently or not at all. NFS supports locks, but the semantics around lock recovery after a client dies have historically been surprising. If the participants are on different machines over a network, test the behaviour of your specific filesystem before relying on it.
Two smaller habits are worth forming. Give the lock file a name that identifies its purpose, since /var/lock accumulates files and a generic name makes stale locks hard to trace. And keep the critical section short, because every participant holds the lock while it runs and a long critical section turns a contention problem into a queueing problem.
Project hygiene and how it compares with the alternatives
The repository is small and well organised, and the tooling reflects a maintained project. The Makefile has a default target that runs lint, test and build_cross_os in sequence, a test target running go test with verbose output and coverage, a test_race target running the suite with the race detector under CGO_ENABLED=1, and a lint target running golangci-lint. There is a .golangci.yml configuration, a build.sh for the cross-compilation target, a SECURITY.md, and a zizmor.yml file, which is the configuration for the GitHub Actions security scanner.
The cross-compilation target matters for a lock library specifically. A file lock has to work on the platform you ship to, and the fact that the project builds for other operating systems on every change is a signal that the platform-specific code keeps compiling.
The release history shows steady maintenance: v0.12.1 in July 2024, v0.13.0 in October 2025 and v0.13.1 in August 2026, with the last push on 2026-10-01. A minor version bump in a library with this small an API surface usually means a behavioural change, so reading the changelog before upgrading is worth the minute.
The licence is BSD 3-Clause, which is permissive with no obligation beyond keeping the notice, and the README points at the LICENSE file.
The project history is worth noting because it explains the name. It was originally github.com/theckman/go-flock and was transferred to Gofrs by the original author, Tim Heckman, which is why the maintainers are the Gofrs organisation rather than an individual.
On the alternatives, the standard library has no file locking at all, so a dependency like this is unavoidable if you need one. The other option is a lock implemented in a database, a Redis key with a timeout, or a coordination service, and those solve a different problem: they work across machines that do not share a filesystem, and they can be inspected and released by an administrator. A file lock is the right answer when the participants share a filesystem and you want the lock to disappear if a participant dies.
Editorial conclusion
Adopt gofrs/flock if you need a mutex that survives a process crash or coordinates between separate programs, since an in-process sync.Mutex does neither. Skip it if you only need to serialise goroutines inside one process, because a channel or a mutex is simpler and faster. Verify first by calling TryLock in two processes on the same path and confirming the second returns false rather than blocking, and by checking that your filesystem honours advisory locks, since network filesystems often do not.
Frequently asked questions
What does gofrs/flock provide?
It implements a thread-safe file lock, including a non-blocking TryLock function that allows locking without blocking execution, plus the blocking Lock method and the release call, and a lock object created from a path.
How do I install gofrs/flock and use it?
Run go get -u github.com/gofrs/flock, create a lock with flock.New on the path you want to lock, then call TryLock and check the returned boolean and error separately. Release the lock when the work is done.
What is the difference between the boolean and the error from TryLock?
The error is a real failure such as a permission problem or an invalid path. The boolean is the normal outcome where another holder already has the lock, which is not an error. Checking the error first and then the boolean is what the README example does.
Why does gofrs/flock have separate flock and fcntl implementations for Unix?
POSIX offers two advisory locking mechanisms with different semantics: flock associates the lock with an open file description while fcntl associates it with the process, so closing one descriptor releases all fcntl locks. The repository has flock_unix.go and flock_unix_fcntl.go to handle that difference, plus flock_windows.go and flock_others.go.
When is a file lock better than a mutex or a database lock?
A file lock coordinates separate processes on a shared filesystem and is released by the operating system if a holder dies, which a mutex is not. A database or Redis based lock is the better answer when the participants do not share a filesystem or when an administrator needs to inspect and release the lock.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/gofrs-flock)Was this useful?