Short answer: fcntl and msvcrt are not interchangeable Python APIs. fcntl is the Unix interface and msvcrt is the Windows interface; each exposes the locking model of its operating system. A cross-platform program must select the implementation at runtime or use a library such as Portalocker.
The correct design starts with the coordination rule you need: exclusive access to a whole file, concurrent readers with one writer, or a lock on a byte range. Then keep that lock for the entire critical section, make every cooperating process use the same path and convention, and release it reliably.
As an Amazon Associate I earn from qualifying purchases.
What the two modules actually provide
Python documents fcntl as an interface to Unix fcntl() and ioctl() routines. The module is not available on Windows. Python’s msvcrt module exposes Microsoft C runtime facilities and is available on Windows, not Unix-like systems. Therefore this code is not portable:
import fcntl
On Windows, the import itself fails before your application can choose a lock. Conversely, importing msvcrt is not a Unix solution. A portable application needs a platform branch or a cross-platform wrapper.
#1 Best Overall
Both families can coordinate cooperating processes, but they differ in lock scope, arguments, sharing options, ownership and close behaviour. Treating them as equivalent calls is a common source of false safety.
Choose the coordination goal first
Before choosing an API, define what must be protected.
- Whole-file mutual exclusion: one process at a time may enter a read/modify/write section.
- Reader/writer coordination: several readers may proceed, but a writer must exclude readers and other writers.
- Byte-range coordination: different processes own separate records or regions in one file.
- Process or application mutex: the file is merely a convenient lock identity; its contents are not the protected data.
fcntl.flock is commonly used for whole-file locks on Unix. fcntl.lockf wraps record-lock operations and accepts a length, start offset and reference origin, so it can cover a byte range. Windows msvcrt.locking also locks a selected portion of a file, using the descriptor, current file position, mode and byte count. A wrapper may choose its own range and lifetime rules, so read its documentation rather than assuming it mirrors either primitive exactly.
fcntl.flock: whole-file locking on Unix
The usual interface is:
fcntl.flock(fd, operation)
Typical operations are fcntl.LOCK_EX for an exclusive lock, fcntl.LOCK_SH for a shared lock, and fcntl.LOCK_UN to unlock. Add fcntl.LOCK_NB when contention should fail immediately instead of waiting.
Blocking exclusive lock
import fcntl
with open("state.json", "r+", encoding="utf-8") as file:
fcntl.flock(file.fileno(), fcntl.LOCK_EX)
try:
# Read, modify and write while the lock is held.
file.seek(0)
data = file.read()
file.seek(0)
file.write(data)
file.truncate()
finally:
fcntl.flock(file.fileno(), fcntl.LOCK_UN)
The lock must cover the complete critical section, not only the write call. Opening, reading, calculating a new value, writing and truncating outside the lock allows another process to observe or overwrite an intermediate state.
Non-blocking contention
import errno
import fcntl
with open("state.json", "r+", encoding="utf-8") as file:
try:
fcntl.flock(file.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
except OSError as error:
if error.errno in (errno.EACCES, errno.EAGAIN):
print("Another process owns the lock")
else:
raise
else:
try:
# Critical section
pass
finally:
fcntl.flock(file.fileno(), fcntl.LOCK_UN)
The documented non-blocking failure is an OSError; check the platform’s documented errno values rather than treating every I/O error as ordinary contention. Permission failures, a missing path or a damaged descriptor need different handling.
Rank #2
Advisory means every participant must cooperate
POSIX flock is advisory in the behaviour documented by Portalocker: a process that ignores the locking convention can still access the file. The lock protects a protocol, not an uncooperative program. Every writer, and every reader that must see a consistent state, has to acquire a compatible lock before touching the file.
fcntl.lockf: record locks and ranges
lockf is a record-lock wrapper rather than simply another spelling of flock:
fcntl.lockf(fd, cmd, len=0, start=0, whence=0)
The len, start and whence arguments define the region. A length of zero conventionally extends from the starting point to the end of the file. The command can request an exclusive or shared lock, unlock a region, and, where supported, use a non-blocking variant.
Record locks are useful when independent records can be updated concurrently, but they introduce more decisions: every participant must calculate the same offsets and lengths, use the same command family, and understand how the operating system associates ownership with processes and descriptors. Do not assume that a region locked with lockf has identical ownership or close semantics to one locked with flock.
msvcrt.locking: Windows byte-range locking
Windows code uses:
msvcrt.locking(fd, mode, nbytes)
The function locks a portion of the file identified by the descriptor, the mode and the number of bytes. The range begins at the descriptor’s current file position, so seeking and choosing nbytes are part of the lock protocol. A caller that locks 100 bytes at offset zero is not coordinating with another caller that locks 100 bytes at offset 100.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Keep the position and range explicit
import msvcrt
with open("state.dat", "r+b") as file:
file.seek(0)
msvcrt.locking(file.fileno(), msvcrt.LK_LOCK, 1)
try:
file.seek(0)
payload = file.read()
file.seek(0)
file.write(payload)
file.flush()
finally:
file.seek(0)
msvcrt.locking(file.fileno(), msvcrt.LK_UNLCK, 1)
This example deliberately uses a one-byte lock marker and keeps the seek operations visible. Your program must document the chosen marker range and open mode. The Windows API’s locking modes include blocking and non-blocking forms; use the mode that matches your contention policy and handle the documented Windows error separately from permission or read/write failures.
msvcrt.locking is a byte-range API, not a portable equivalent of Unix whole-file flock. If you want to serialize an entire file, define a range that every process agrees to use or select a library whose abstraction makes that choice for you.
Comparison at a glance
| Decision axis | fcntl.flock / fcntl.lockf |
msvcrt.locking |
|---|---|---|
| Python platform | Unix-like systems; the module is not available on Windows. | Windows; the module is not a Unix implementation. |
| Primary shape | flock commonly covers a whole file; lockf supports record ranges with length, start and origin arguments. |
Locks a range selected by descriptor position, mode and byte count. |
| Shared locking | flock and lockf expose shared-lock options where the platform supports them. |
The standard-library API is range-oriented; Portalocker documents that Windows msvcrt has no true shared-lock mode. |
| Contention policy | Blocking and non-blocking flags are available; non-blocking failure is reported as OSError. |
Choose a blocking or non-blocking locking mode and handle Windows-specific contention errors. |
| Portability | Requires Unix-specific code or an abstraction. | Requires Windows-specific code or an abstraction. |
Do not mix lock primitives casually
Portalocker documents important differences in ownership and close semantics between flock and lockf, and warns that mixing primitives is not portable across systems. Select one convention for a shared file and make every participant follow it. A process using flock may not coordinate as expected with another using lockf, and neither convention automatically coordinates with code that uses a different path, a different range or no lock at all.
Use one canonical lock identity. If the data file can be replaced atomically, decide whether the lock belongs to the data path or to a separate, stable lock file. Ensure all processes resolve that identity the same way; a relative path from different working directories can name different files.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →A small platform-dispatch pattern
If adding a dependency is not appropriate, isolate the operating-system code behind two functions. The example below uses a whole-file intent on Unix and a documented marker range on Windows; adapt the range and open mode to your file format.
import os
from contextlib import contextmanager
@contextmanager
def locked_file(path):
if os.name == "nt":
import msvcrt
with open(path, "a+b") as file:
file.seek(0)
msvcrt.locking(file.fileno(), msvcrt.LK_LOCK, 1)
try:
yield file
finally:
file.seek(0)
msvcrt.locking(file.fileno(), msvcrt.LK_UNLCK, 1)
else:
import fcntl
with open(path, "a+", encoding="utf-8") as file:
fcntl.flock(file.fileno(), fcntl.LOCK_EX)
try:
yield file
finally:
fcntl.flock(file.fileno(), fcntl.LOCK_UN)
with locked_file("state.dat") as file:
# Complete read/modify/write operation goes here.
pass
This is a teaching pattern, not a claim that both operating systems have identical semantics. In production, specify the file format, encoding, marker range, timeout behaviour and recovery policy. Test the same code with multiple processes on every supported operating system.
Using Portalocker for one Python-facing interface
Portalocker selects platform mechanisms for you: its documentation identifies POSIX fcntl.flock (or optional lockf) and Windows msvcrt by default. Exclusive Windows locks work without extra dependencies. Its documented shared-lock support on Windows requires the optional Win32 extra:
python -m pip install "portalocker[win32]"
Confirm the installed Portalocker release and supported Python versions before pinning a deployment, because project documentation and package behaviour are release-sensitive.
Context-managed file lock
import portalocker
with portalocker.Lock("state.json", mode="a+", timeout=5) as file:
file.seek(0)
text = file.read()
file.seek(0)
file.write(text)
file.truncate()
file.flush()
The context manager keeps the lock around the complete block, closes the file and releases the lock when control leaves the block, including an exception. A timeout expresses how long the caller will wait; decide whether a timeout should be retried, surfaced to a user or treated as a failed job.
Portalocker also documents distinct lock classes: an ordinary file lock, a reentrant lock, a temporary-file mutex, a PID-file lock and a bounded semaphore. They address different coordination goals and should not be substituted for one another without checking lifecycle and failure semantics.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.File mode, flushing and critical-section boundaries
Open for the operation you intend
Use a mode that permits the operation: read-only for a reader that never writes, update modes such as r+ or r+b for existing data, and an append-capable mode when creating a lock target is acceptable. Do not imply that one open mode works on every platform or that text and binary modes have identical byte offsets. Windows byte ranges are bytes; text translation can make offsets unsuitable for record locking.
Lock before reading shared state
For a read/modify/write transaction, acquire the lock before the first read. Seek, read, calculate, write, truncate if needed, flush and then release. If another process can replace or rename the data path, define how the lock identity follows that operation.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallAlways release in a cleanup path
Use try/finally or a context manager. Exceptions in parsing, validation or writing must not skip normal release. A process crash is a separate failure mode; do not claim that a lock alone provides application-level recovery or transaction durability.
Best Value
Failure modes and fixes
ModuleNotFoundError: fcntlon Windows: expected platform mismatch. Dispatch on the platform or use an abstraction.- Two writers still overwrite one another: one participant may be bypassing the lock, using another path, selecting another byte range or using a different primitive. Audit every reader and writer.
- Unexpected immediate failure: a non-blocking flag or mode was selected. Distinguish expected contention from permissions, missing files and other
OSErrorconditions. - Windows processes do not agree: verify that each call seeks to the same offset, uses the same byte count and unlocks the same region.
- Data is inconsistent after an exception: the lock controls concurrency, not the atomicity of your file format. Use a format and write strategy that can recover from an interrupted update.
- Works locally but not on a mounted share: do not generalize local filesystem behaviour to NFS, SMB or cloud-mounted volumes. Guarantees depend on the operating system, mount options and filesystem implementation and require separate validation.
Practical decision guide
- Only Unix-like systems: use
fcntl.flockfor a cooperative whole-file critical section, orlockfwhen you genuinely need record ranges. - Only Windows: use
msvcrt.lockingwhen you can specify and test the exact byte range and mode. - One codebase for both: isolate platform branches behind a small interface, or use Portalocker and document its selected mechanism and version.
- Shared Windows readers: check the wrapper’s support and install requirements; Portalocker documents an optional
pywin32extra for shared locks. - Distributed or network storage: stop at a platform-specific design review. Neither standard-library example establishes universal mutual exclusion across every network filesystem.
Checklist before shipping
- Every participant uses the same canonical path or lock identity.
- The platform implementation is selected without importing an unavailable module.
- The protected region is explicit: whole file or exact byte range.
- All participants use the same primitive and compatible shared/exclusive policy.
- The lock covers the complete read/modify/write operation.
- Contention, permission errors and I/O errors are handled separately.
- Release occurs in a
finallyblock or context manager. - File mode, encoding, byte offsets, flushing and replacement behaviour are documented.
- Multiple-process tests run on every supported operating system and storage type.
Frequently Asked Questions
Can I import both fcntl and msvcrt and choose later?
You can conditionally import the module you need, but an unconditional import of a platform-specific module makes the program fail on systems where it is unavailable. Put the import inside the platform branch or use a wrapper.
Is fcntl.flock automatically enforced against every program?
No. POSIX flock is advisory in the documented Portalocker behaviour. Programs that must coordinate have to honor the same locking protocol.
Should I use flock and lockf together?
No, not casually. Their ownership and close semantics differ, and mixing primitives is not portable. Choose one convention for all participants.
Recommended Free Tools
Does a file lock make network storage safe?
Not universally. NFS, SMB and cloud-mounted volumes can have platform- and mount-specific behaviour. Validate the exact operating system, filesystem and mount configuration before relying on distributed locking.
The Bottom Line
Use fcntl.flock or lockf for a Unix-specific design, msvcrt.locking for a deliberately specified Windows byte range, and a maintained abstraction when one application must run on both. Whichever route you choose, one shared path, one locking convention, a complete critical section and guaranteed release matter more than making the APIs look identical.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

