By referring to flock(): removing locked file without race condition? and Will flock'ed file be unlocked when the process die unexpectedly? ,
I produce the following code. My intention is to allow only single thread / single process to run the critical section code, within any given time.
<?php
// Exclusive locking based on function parameters.
$lockFileName = '/tmp/cheok.lock';
// Create if doesn't exist.
$lockFile = fopen($lockFileName, "w+");
if (!flock($lockFile, LOCK_EX)) {
throw new \RumtimeException("Fail to perform flock on $lockFileName");
}
echo "start critical section...\n";
sleep(10);
echo "end critical section.\n";
// Warning: unlink(/tmp/cheok.lock): No such file or directory in
unlink($lockFileName);
flock($lockFile, LOCK_UN);
I will always get the warning
Warning: unlink(/tmp/cheok.lock): No such file or directory in
when the 2nd waiting process continues his execution, the 1st process already remove the physical disk file. The 2nd process tries to unlink
file, which is already deleted by the 1st process.
And, what if, there is 3rd process join in, and tries the perform fopen
while 2nd process is trying to perform unlink
?
In short, what is the correct way to perform lock file cleanup?
The question is tagged with multithreading, so I'm going to answer the question in the context of multi-threading with pthreads.
First, a little bit about file locking. The PHP manual fails to fully explain what an advisory lock is.
Taken from the man page for flock
flock() places advisory locks only; given suitable permissions on a file, a process is free to ignore the use of flock() and perform I/O on the file.
In a ecosystem filled with people who simultaneously don't know much about permissions, and use cron jobs to execute long running scripts, this can be the cause of much pain ... although nobody really knows it.
In the context of multithreading (with pthreads), you want to stay well away from file locking, pthreads provides a much superior API for implementing mutual exclusion.
Follows is example code that creates a number of Threads, and implements mutual exclusion:
<?php
class Test extends Thread {
public function __construct(Threaded $monitor) {
$this->monitor = $monitor;
}
public function run () {
$this->monitor->synchronized(function(){
for ($i = 0; $i < 1000; $i++)
printf("%s #%lu: %d\n",
__CLASS__, $this->getThreadId(), $i);
});
}
private $monitor;
}
$threads = [];
$monitor = new Threaded();
for ($i = 0; $i < 8; $i++) {
$threads[$i] = new Test($monitor);
$threads[$i]->start();
}
foreach ($threads as $thread)
$thread->join();
Because all the Threads share the same $monitor
, you can use the synchronization built into Threaded
objects to implement reliable mutual exclusion, causing the output to resemble:
Test #140561163798272: 0
<-- snip for brevity -->
Test #140561163798272: 999
Test #140561151424256: 0
<-- snip for brevity -->
Test #140561151424256: 999
Test #140561138841344: 0
<-- snip for brevity -->
Test #140561138841344: 999
Test #140561059149568: 0
<-- snip for brevity -->
Test #140561059149568: 999
Test #140561050756864: 0
<-- snip for brevity -->
Test #140561050756864: 999
Test #140561042364160: 0
<-- snip for brevity -->
Test #140561042364160: 999
Test #140561033971456: 0
<-- snip for brevity -->
Test #140561033971456: 999
Test #140561025578752: 0
<-- snip for brevity -->
Test #140561025578752: 999
We can see that each Thread
is mutually excluded from executing the code in the Closure
passed to Threaded::synchronized
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With