Concurrent accesses to shared resources are guarded by synchronization primitives such as mutexes to prevent data races. The section of code where
a mutex is held is called the critical section. Critical sections are generally designed to be as small as possible, allowing concurrent threads to
progress.
It’s usually unintentional to perform blocking operations inside a critical section because the operation might block for long or even
indefinitely, degrading performance or causing a deadlock.
#include <cstdio> // printf()
#include <cstdlib> // atoi()
#include <mutex>
#include <unistd.h> // sleep()
std::mutex m;
int load_shared_resource(); // Guarded by mutex 'm'
// Some time-intensive computation.
void do_expensive_work(int value, FILE *fd) {
char buf[4] = "";
std::fgets(buf, sizeof(buf), fd);
int sum = value + std::atoi(buf);
std::printf("value + line: %d\n", sum);
}
void worker_thread(FILE *fd) {
std::scoped_lock guard(m);
int value = load_shared_resource();
// Mutex 'm' could have been released here.
do_expensive_work(value, fd);
} // Mutex 'm' only released here, after 'do_expensive_work' is returned.
Usually, blocking operations involve I/O operations, such as reading or writing a file or socket or sleeping for some specified time.
What is the potential impact?
Doing time-intensive operations while holding one or multiple locks will prevent concurrent threads from making progress updating the shared
resource. This can lead to "bottlenecks" and the under-utilization of the hardware capabilities.