The scenario is, in a macOS app (primarly), main thread needs to wait for some time for a certain 'event'. When that event occurs, the main thread is signaled, it gets unblocked and moves on.
An example is, during shutdown, a special thread known as shutdown thread waits for all other worker threads to return (thread join operation). When all threads have returned, the shutdown thread signals the main thread, which was waiting on a timer, to continue with the shutdown flow. If shutdown thread signals the main thread before the later's timer expires, it means all threads have returned. If main thread's timer expires first, it means some threads have failed to join (probably stuck in infinite loop due to bug, disk I/O etc.).
This post is to understand how main thread can wait for some time for the shutdown thread. There are two ways: a) dispatch_semaphore_t b) pthread conditional variable (pthread_cond_t) and mutex (pthread_mutex_t).
Expanding a bit on option (b) using conditional variable and mutex:
// This method is invoked on the main thread
bool ConditionSignal::TimedWait() noexcept
{
struct timespec ts;
pthread_mutex_t * mutex = (pthread_mutex_t *) (&vPosix.vMutexStorage[0]);
pthread_cond_t * cond = (pthread_cond_t *) (&vPosix.vCondVarStorage[0]);
// Set the timer to 3 sec.
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += 3;
pthread_mutex_lock(mutex);
LOG("Main thread has acquired the mutex and is waiting!");
int wait_result = pthread_cond_timedwait(cond, mutex, &ts);
switch (wait_result) {
case 0:
LOG("Main thread signaled!");
return true;
case ETIMEDOUT:
LOG("Main thread's timer expired!");
return false;
default:
LOG("Error: Unexpected return value from pthread_cond_timedwait: " + std::to_string(wait_result));
break;
}
return false;
}
// This method is invoked on shutdown thread after all worker threads have returned.
void ConditionSignal::Raise() noexcept
{
pthread_mutex_t * mutex = (pthread_mutex_t *) (&vPosix.vMutexStorage);
pthread_cond_t * cond = (pthread_cond_t *) (&vPosix.vCondVarStorage);
pthread_mutex_lock(mutex);
LOG("[Shutdown thread]: Signalling main thread...");
pthread_cond_signal(cond);
pthread_mutex_unlock(mutex);
}
Both options allow the main thread to wait for some time (for shutdown thread) and continue execution. However, when using dispatch_semaphore_t, I get the following warning:
Thread Performance Checker: Thread running at User-interactive quality-of-service class waiting on a lower QoS thread running at Default quality-of-service class. Investigate ways to avoid priority inversions
Whereas, with conditional variables, there are no warnings. I understand the warning - holding the main thread can prevent it from responding to user events, thus causing the app to freeze. And in iOS, the process is killed if main thread takes more than 5 secs to return from applicationWillTerminate(_:) delegate method. But in this scenario, the main thread is on a timed-wait, for some milliseconds i.e., it is guaranteed to not get blocked indefinitely.
While this is described for macOS, this functionality is required for all Apple OSes. What is the recommend way?