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.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

Short answer: fcntl and msvcrt are not interchangeable Python APIs. fcntl is available on Unix-like systems and exposes whole-file flock operations plus byte-range lockf operations. msvcrt is Windows-only and locks a specified byte range. Code that must run on both platforms must choose an implementation at runtime or use an abstraction such as Portalocker. Whichever API you use, every cooperating process must open the same lock target, follow the same protocol, and hold the lock for the complete critical section.

What file locking is supposed to protect

A file lock coordinates processes; it does not automatically make a file transactional, durable, or safe across every filesystem. The usual goal is to protect a critical section such as reading a counter, changing it, and writing it back. Without coordination, two processes can read the same old value and overwrite one another’s updates.

Before choosing an API, decide which of these problems you actually have:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Single-owner access: only one process may enter a section at a time. This normally calls for an exclusive lock.
  • Concurrent readers: several readers may proceed, but a writer must wait. This requires a shared/exclusive protocol and a primitive that supports the required lock modes.
  • Byte-range coordination: independent records occupy different regions of one file. A range lock can protect only the bytes belonging to a record.
  • Presence or leader signalling: a process needs to advertise ownership or liveness. A lock file can be part of that design, but the lock itself is still an operating-system coordination mechanism.

The standard library does not offer one identical file-locking API on every operating system. Importing fcntl unconditionally is therefore not a Windows solution, and importing msvcrt unconditionally fails on Unix-like systems.

fcntl on Unix-like systems

Python’s fcntl module is an interface to Unix fcntl() and ioctl() facilities. It is available on Unix-like platforms, not on Windows. Two interfaces are especially relevant to file locking.

fcntl.flock: whole-file operations

fcntl.flock(fd, operation) requests an advisory lock operation on an open file descriptor. Typical flags are LOCK_SH for shared access, LOCK_EX for exclusive access, and LOCK_UN to unlock. Add LOCK_NB when the caller should fail rather than wait.

“Whole-file” describes the scope of the flock request: the lock applies to the file represented by the descriptor rather than to a caller-selected offset and length. The lock is still a protocol. On POSIX systems, the usual flock contract is advisory, meaning a process that never attempts to acquire the lock can continue to read or write. Every participant that must coordinate has to honor the convention.

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

fcntl.lockf: record-lock wrapper

fcntl.lockf(fd, cmd, len=0, start=0, whence=0) wraps record-lock operations. The len, start, and whence arguments let you describe a byte range. A length of zero conventionally means from the starting position to the end of the file. Commands include shared, exclusive, unlock, and non-blocking variants, subject to the platform’s implementation.

lockf and flock are different locking families. Their ownership, inheritance, and close behavior are not interchangeable on every Unix system. Do not have one process use flock while another assumes that a lockf request protects the same critical section. Pick one primitive and use it consistently for all participants.

Blocking and non-blocking behavior

A blocking exclusive request waits until an incompatible lock is released. A non-blocking request returns immediately with an OSError when the lock cannot be obtained. If your application treats contention as an expected condition, handle that condition separately from permission failures, a missing path, a bad descriptor, or a full disk. For portable error handling, inspect the documented errno values for the target platform rather than assuming one exception class or numeric value everywhere.

msvcrt.locking on Windows

Python’s msvcrt module is available on Windows. Its locking(fd, mode, nbytes) function locks a portion of a file identified by a C runtime file descriptor, the current file position, a locking mode, and a byte count.

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

The range is central to the API. Position the descriptor at the first byte to protect, then pass the number of bytes. Modes include blocking and non-blocking forms such as LK_LOCK and LK_NBLCK, with LK_UNLCK used to release the range. The exact mode and retry behavior are Windows-specific; read the Python version’s msvcrt documentation when you need a particular timeout or failure guarantee.

Because the API locks a selected range, two callers can accidentally protect different bytes and proceed concurrently. A common lock-file convention is to ensure a one-byte file exists, seek to offset zero, and have every participant lock that same byte. If you instead lock data records, document the offsets and lengths so that every reader and writer calculates them identically.

Windows locking behavior should not be described as a drop-in equivalent to POSIX advisory flock. The standard-library documentation specifies the byte-range interface; enforcement and sharing details belong to the Windows file-opening and filesystem model. Test the exact combination of access flags, sharing modes, and filesystem used by your application.

Decision table: fcntl versus msvcrt

Decision axis fcntl.flock / fcntl.lockf msvcrt.locking
Availability Unix-like Python builds; the module is not provided on Windows. Windows Python builds; the module is not a Unix replacement.
Scope flock is whole-file; lockf supports a selected range through length, start, and origin arguments. A selected range beginning at the descriptor’s current position and extending for the requested byte count.
Shared locks flock and lockf expose shared-lock flags, subject to platform behavior. The basic API is range-oriented; a true shared-lock design may require a Windows-specific mechanism or a library abstraction.
Non-blocking mode Add LOCK_NB; an unavailable lock is reported as an OSError condition. Use a non-blocking mode such as LK_NBLCK; handle the documented Windows error condition.
Portability Requires Unix-specific code or a wrapper. Requires Windows-specific code or a wrapper.

A minimal cross-platform lock with the standard library

The following example uses a persistent lock file and protects one byte. It chooses the implementation only after checking the operating system, keeps the lock across the entire critical section, and releases it in a context manager. The one-byte convention is deliberate: all cooperating processes must use the same path and the same byte.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
from contextlib import contextmanager
import os
import sys

@contextmanager
def exclusive_lock(path, blocking=True):
    # a+b permits reading and writing and does not truncate an existing file
    with open(path, 'a+b') as lock_file:
        # msvcrt.locking needs a byte to exist in the file
        if os.path.getsize(path) == 0:
            lock_file.write(b'0')
            lock_file.flush()
        lock_file.seek(0)

        if os.name == 'nt':
            import msvcrt
            mode = msvcrt.LK_LOCK if blocking else msvcrt.LK_NBLCK
            try:
                msvcrt.locking(lock_file.fileno(), mode, 1)
            except Exception:
                # The file object still closes below; do not hide the
                # original locking or I/O error from the caller.
                raise
            try:
                yield lock_file
            finally:
                lock_file.seek(0)
                msvcrt.locking(lock_file.fileno(), msvcrt.LK_UNLCK, 1)
        else:
            import fcntl
            operation = fcntl.LOCK_EX
            if not blocking:
                operation |= fcntl.LOCK_NB
            fcntl.flock(lock_file.fileno(), operation)
            try:
                yield lock_file
            finally:
                fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)

# Keep every read/modify/write step inside this block.
with exclusive_lock('/var/tmp/my-app.lock'):
    update_shared_state()

This is a teaching pattern, not a universal production recipe. The path must be writable on both platforms; /var/tmp is only an example and is not a portable location. Choose an application-specific directory with known permissions, and make sure every process resolves the same absolute path. Relative paths can identify different files when processes start in different working directories.

Why the lock surrounds the whole operation

Opening the file, acquiring a lock, reading state, computing a new value, writing, flushing, and releasing are one logical transaction in this example. Moving the unlock before the write recreates the race the lock was intended to prevent. If the operation raises, the finally block still attempts to release the lock and the outer with statement closes the descriptor.

Making contention a normal branch

For a try-once operation, call exclusive_lock(path, blocking=False) and catch the platform’s documented “would block” error. Report “another process is using the resource” separately from a permission error or a corrupt path. If you need a timeout, implement a bounded retry loop with a monotonic deadline and a short sleep; do not busy-spin. A retry loop should still distinguish a lock that remains busy from an I/O failure that will not recover by waiting.

Using a shared abstraction instead of branching

Portalocker presents a common Python-facing interface and selects platform mechanisms underneath. Its documentation describes POSIX use of fcntl.flock (or optional lockf) and Windows use of msvcrt by default. This removes import branching from application code, but it does not remove the need to understand lock scope, advisory behavior, paths, or failure handling.

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

For a basic context-managed lock, the shape is:

import portalocker

with portalocker.Lock('my-app.lock', timeout=10):
    update_shared_state()

Install the package according to the version you have selected. Portalocker documents the optional Windows extra for shared-lock support with the command python -m pip install 'portalocker[win32]'. Its documentation states that ordinary Windows exclusive locks work without that extra, while shared Windows locks use optional Win32 support. Confirm the installed release and supported Python range before copying a command into deployment documentation because package behavior and requirements can change.

The library also documents several distinct lock classes, including an ordinary file lock, a reentrant lock, a temporary-file mutex, a PID-file lock, and a bounded semaphore. They solve different coordination problems. A PID-file lock, for example, carries process-identity concerns that are not present in a simple critical-section lock; a semaphore limits a count of simultaneous users rather than enforcing one owner. Choose the class that matches the resource, rather than treating every class as a spelling variation of the same lock.

Lock-file design details that prevent subtle races

Keep the lock path stable

Every participant must use the same file identity. Locking data.json.lock in one process and ./data.json.lock from a different directory may select different files. Symlinks, case differences on case-insensitive filesystems, container mount paths, and per-user temporary directories can also defeat an otherwise correct protocol. Resolve and configure one canonical location where practical.

Do not delete a lock file as a routine cleanup step

The operating-system lock is released when the descriptor is explicitly unlocked and normally when the process closes it. The directory entry can remain. Keeping a zero- or one-byte lock file avoids a race in which one process unlinks the path while another still holds an open descriptor, after which a third process creates a new file and locks a different inode. Remove a lock file only with a design that proves no process can still be using it.

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

Open with compatible access

The descriptor must be opened in a mode that permits the intended operation. A read-only descriptor may be unsuitable for a Windows locking call or for a protocol that writes a marker. Conversely, opening a data file for update can alter its metadata or truncate it if the wrong mode is chosen. A separate lock file is often safer because locking it does not change the contents of the protected data file.

Use one primitive and one range convention

All participants should agree on flock versus lockf, or on the exact Windows range and mode. Mixing primitives is not portable, and a library’s wrapper may make a different range choice than hand-written code. Document the convention beside the code and in the operational runbook.

Advisory locks, enforcement, and bypasses

On Unix-like systems, an advisory lock works only when every relevant process checks it. A process that opens the data file and writes without acquiring the agreed lock can still interfere. This is why the lock protocol belongs in every reader and writer, including maintenance scripts and one-off migration tools.

Do not infer that a successful local test proves distributed mutual exclusion. Network filesystems, SMB shares, NFS mounts, cloud-synchronized folders, and container volumes can have implementation-specific lock behavior, caching, failover, and disconnect semantics. The available documentation for these APIs does not establish a universal guarantee for every such environment. If the data crosses a network boundary, validate the exact operating system, mount options, server implementation, and failure model before making a deployment guarantee.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Testing a locking implementation

A useful test launches two independent processes that use the same lock path and deliberately hold the lock for a visible interval. The second process should block (or receive the documented non-blocking failure) until the first releases it. Add tests for:

  • normal release after a successful critical section;
  • release when the protected function raises an exception;
  • two processes started from different working directories;
  • blocking and non-blocking contention;
  • an absent lock file and a pre-existing lock file;
  • permission errors and a read-only directory;
  • the same byte-range convention on Windows;
  • the exact filesystem used in production, if it is not local.

Run the suite on both a Unix-like host and Windows. A test that merely imports both modules on one platform misses the most common portability failure: the unavailable module is imported before the platform branch runs.

Common mistakes and their fixes

Importing both modules at the top level

Symptom: a Windows process fails with an import error for fcntl, or a Unix process fails when code expects msvcrt. Fix: import the platform module inside a branch, or use a maintained abstraction.

Locking different files

Symptom: both processes report success even though each appears to acquire a lock. Fix: log the canonical lock path and verify that all processes use the same file identity and permissions.

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

Protecting only part of a transaction

Symptom: occasional lost updates remain after adding a lock. Fix: move the lock acquisition before the first read and release it only after the final write and required flush.

Assuming a lock makes uncooperative code safe

Symptom: a script that bypasses the protocol still corrupts data. Fix: make every writer and reader follow the protocol, or use an enforcement mechanism appropriate to the operating system and threat model.

Using a stale-lock timeout as proof of process death

Symptom: a timeout-based cleanup removes a file while a legitimate process still owns the lock. Fix: distinguish the persistent pathname from the live OS lock, and use a documented PID-file or lease design only when you can validate process identity and failure cases.

How to choose an approach

Situation Recommended shape Reason
Unix-only program protecting one shared file fcntl.flock with a separate lock file Simple whole-file coordination with explicit shared or exclusive modes.
Unix program updating independent records fcntl.lockf with documented ranges Record-lock arguments express byte offsets and lengths.
Windows-only program with a known byte range msvcrt.locking The API directly specifies the starting position and byte count.
One codebase for Unix and Windows A small platform branch or Portalocker Keeps OS-specific imports and semantics in one place.
Shared locks on Windows An abstraction with the documented Windows support installed The basic msvcrt interface is not a portable true shared-lock solution.

Practical checklist

  • Identify whether you need exclusive, shared, or byte-range coordination.
  • Choose Unix-specific code, Windows-specific code, or an abstraction deliberately.
  • Use one canonical lock path and document it.
  • Ensure the file is opened with access compatible with the operation.
  • Acquire the lock before the first read in the critical section.
  • Keep it until the final write and flush are complete.
  • Release it in finally or a context manager.
  • Handle expected contention separately from permission and I/O failures.
  • Do not mix flock, lockf, and Windows range conventions casually.
  • Test normal, exceptional, non-blocking, and cross-process paths on every supported platform.
  • Qualify any network-filesystem guarantee with the exact filesystem and mount configuration.

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.

Free tools Windows power users keep installed

One-click scans. No signup required.

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