This is a list of things that UNIX-like/POSIX-compliant operating systems can do atomically, making them useful as building blocks for thread-safe and multi-process-safe programs without mutexes or read/write locks. The list is by no means exhaustive and I expect it to be updated frequently in the near future.
The philosophy here is to let the kernel do as much of the work as possible. At my most pessimistic, I trust the kernel developers more than myself. More practically, it is foolish to waste CPU time by locking an operation that is already atomic. Added on 2010-01-07.
operation on pathname
The actions below are best left to the local file system. More than a few people have written in to cry foul if any of these techniques are used on an NFS mount. Truth. When multiple kernels are involved, the kernel cannot take care of all the locking well for us. Added on 2010-01-06.
mv -Tatomically alters the targetto the directory pointed to byAnd is indispensable when deploying new code. Update 2010-01-06: Both operands are symlinks. (So it’s not a system call, it’s still useful.)A reader pointed this outRemoved on 2010-01-06:ln -TfsAccomplishes the same task even without the second symlink. Added on 2010-01-06.strace(1)It turns out thatln -Tfsactually callssymlink(2),unlink(2)Andsymlink(2)Once again, it has been disqualified from this page.mv -Tcall endsrename(2)which can atomically replace. WARNING 2013-01-07: This does not apply to Mac OSmv(1)doesn’t callrename(2).mv(1).link(oldpath, newpath)Creates a new hard link callednewpathpointing to the same inodeoldpathAnd increments the link number by one. It will fail with error codeEEXISTIfnewpathThis already exists, making it a useful mechanism for locking a file between threads or processes on a name they can all agree on.newpath. I prefer this technique for entire file locking because the lock is visible.ls(1).link(2).symlink(oldpath, newpath)works greatlink(2)But creates a symbolic link to a new inode instead of a hard link to the same inode. Symbolic links can point to directories, which hard links cannot, making them a perfect analogy.link(2)When locking entire directories. It will fail with error codeEEXISTIfnewpathalready exists, which makes it a perfect analogylink(2)This works for directories too. Beware of symbolic links whose target inode has been removed (“dangling” symbolic links) –open(2)will fail with error codeENOENT. It should be mentioned that inodes are a limited resource (this particular machine has 1,245,184 inodes).symlink(2). Added on 2010-01-07rename(oldpath, newpath)Provided that, the pathname can be changed atomically.oldpathAndnewpathAre on the same file system. It will fail with error codeENOENTIfoldpathdoes not exist, enables interprocess locking to a large extentlink(oldpath, newpath)Above. I find this technique more natural when the related files areunlinkLater Ed.rename(2).open(pathname, O_CREAT | O_EXCL, 0644)Creates and opens a new file. (Don’t forget to set the mode in the third argument!)O_EXCLCauses it to fail with error codeEEXISTIfpathnameIs present. This is a useful way of deciding which process should handle a task: whichever process successfully creates the file.open(2).mkdir(dirname, 0755)Creates a new directory but fails with error codeEEXISTIfdirnameIs present. It provides similar mechanism for directorieslink(2)open(2)withO_EXCLProvides files.mkdir(2). Added on 2010-01-06; Edited 2013-01-07.
operations on file descriptors
fcntl(fd, F_GETLK, &lock),fcntl(fd, F_SETLK, &lock)Andfcntl(fd, F_SETLKW, &lock)Allow collaborative processes to lock areas of a file to serialize their access.lockis of typestruct flockand describes the type of lock and the area to be locked.F_SETLKWParticularly useful because it blocks the calling process until the lock is obtained. There is a “mandatory locking” mode but Linux’s implementation is unreliable as it is subject to race conditions.fcntl(2).fcntl(fd, F_GETLEASE)Andfcntl(fd, F_SETLEASE, lease)Tell the kernel to notify the calling processSIGIOWhen another processopens ortruncateis the file referenced byfd. When that signal comes, the leash must be removedfcntl(fd, F_SETLEASE, F_UNLCK).fcntl(fd, F_NOTIFY, arg)Similar but does not block other processes, so it is not useful for synchronization.fcntl(2).mmap(0, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)Returns a pointer from which the contents of the file can be read and written by normal memory operations. from repeated usemsync(addr, length, MS_INVALIDATE)Data written in this manner can be shared between processes that both map to the same file.mmap(2),msync(2).
operations on virtual memory
__sync_fetch_and_add,__sync_add_and_fetch,__sync_val_compare_and_swapAnd friends provide a full barrier so that “no memory operand will be moved forward or backward throughout the operation.” These operations are the basis of most (all?) lock-free algorithms. GCC Nuclear Construction.
Anything I should add to my repertoire? Race condition? Let me know at r@rcrowley.org or @rcrowley And I will fix it.
<a href