DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowHalloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix Now×
FREEDOM251

Freedom251 › Blog › concurrency

Python File Locks Across Unix and Windows: A From-Scratch fcntl vs msvcrt Guide

By Freedom251 Team · · 10 min read

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.Support on Ko-Fi

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Always 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.

Failure modes and fixes

  • ModuleNotFoundError: fcntl on 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 OSError conditions.
  • 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

  1. Only Unix-like systems: use fcntl.flock for a cooperative whole-file critical section, or lockf when you genuinely need record ranges.
  2. Only Windows: use msvcrt.locking when you can specify and test the exact byte range and mode.
  3. One codebase for both: isolate platform branches behind a small interface, or use Portalocker and document its selected mechanism and version.
  4. Shared Windows readers: check the wrapper’s support and install requirements; Portalocker documents an optional pywin32 extra for shared locks.
  5. 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 finally block 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

concurrencyCross-platformfcntlfile lockingmsvcrtPythonUnixWindows

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Outdated Drivers Are Slowing You DownFree scan - exact matches

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.