It sounds simple, but it is very difficult to solve. The reason is racing conditions .
What are racial conditions?
If you open the counter file, read the contents, increase the number of hits and write hits to the contents of the file, many things can happen between all these steps through other visitors opening the same script on your site at the same time. Think about the situation when the first request of visitors (stream) writes β484049β, gets to the char counter file in char and in milliseconds, while β484β is written, the second stream reads this value and increases it to β485β to lose most your pleasant blows.
Do not use global locks!
Perhaps you decided to solve this problem using LOCK_EX . In this case, the second thread must wait until the first one finishes writing to the file. But "waiting" is what you really don't want. This means that every thread and I really mean that every thread has to wait for other threads. You only need some raging bots on your website, many visitors or a temporary problem with i / o on your drive, and no one can load your site until all entries are complete ... and what happens if the visitor will not be able to open your site ... he will update it, causing new expectations / blocking threads ... bottleneck!
Use thread based locks
The only safe solution is to instantly create a new counter file to start threads simultaneously:
<?php // settings $count_path = 'count/'; $count_file = $count_path . 'count'; $count_lock = $count_path . 'count_lock'; // aquire non-blocking exlusive lock for this thread // thread 1 creates count/count_lock0/ // thread 2 creates count/count_lock1/ $i = 0; while (file_exists($count_lock . $i) || !@mkdir ($count_lock . $i)) { $i++; if ($i > 100) { exit($count_lock . $i . ' writable?'); } } // set count per thread // thread 1 updates count/count.0 // thread 2 updates count/count.1 $count = intval(@file_get_contents($count_file . $i)); $count++; //sleep(3); file_put_contents($count_file . $i, $count); // remove lock rmdir($count_lock . $i); ?>
Now you have count/count.1 , count/count.2 , etc. in your counter folder, while count.1 will capture most hits. The reason for this is that race conditions do not occur all the time. They occur only if there were two flows at the same time.
Note. If you see (many) more than two files, this means that your server is very slow compared to the number of visitors that you have.
If you now need full hits, you need to remove them (in this example randomly):
<?php // tidy up all counts (only one thread is able to do that) if (mt_rand(0, 100) == 0) { if (!file_exists($count_lock) && @mkdir($count_lock)) { $count = intval(@file_get_contents($count_file . 'txt')); $count_files = glob($count_path . '*.*'); foreach ($count_files as $file) { $i = pathinfo($file, PATHINFO_EXTENSION); if ($i == 'txt') { continue; } // do not read thread counts as long they are locked if (!file_exists($count_lock . $i) && @mkdir($count_lock . $i)) { $count += intval(@file_get_contents($count_file . $i)); file_put_contents($count_file . $i, 0); rmdir($count_lock . $i); } } file_put_contents($count_file . 'txt', $count); rmdir($count_lock); } } // print counter echo intval(@file_get_contents($count_file . 'txt')); ?>
PS enable sleep(3) and look in the counter folder to simulate a slow server, and you can see how quickly files with several counters grow.