3

Does iOS have any kind of very low level condition lock that does not include locking?

I am looking for a way to signal an awaiting thread from within the Core Audio render thread, without the usage of locks. I was wondering if something low level as a Mach system call might exist.

Right now I have a Core Audio thread that uses a non-blocking thread safe message queue to send messages to another thread. The other thread then pulls every 100ms to see if messages are available in the queue.

But this is very rudimentary and the timing is awful. I could use condition locks, but that involves locking, and I would like to keep any kind of locking out of the rendering thread.

What I am looking for is having the message queue thread wait until the Core Audio render thread signals it. Just like pthread conditions, but without locking and without immediate context switching? I would like the Core Audio thread to complete before the message queue thread is woken up.

Trenskow
  • 3,783
  • 1
  • 29
  • 35
  • You want to do inter-thread signaling without any locking and/or context-switching? If you are queueing audio buffer pointers, why would locking and/or context-switching be any kind of bottleneck? – Martin James Dec 30 '13 at 17:35
  • This is not about the audio rendering. It's about the Core Audio thread sending messages of what's going on: It switched playback from one audio buffer to another (a track change) or it has run out of audio frames to play (buffer under run), etc. So what happens is, it puts this message in a struct, which it writes to a non-blocking circular buffer. The buffer is then checked every now and then (100ms) by another thread, which in return act on the messages it receives from the rendering thread. So I am basically just looking for at way to push to another thread instead of pulling every 100ms. – Trenskow Dec 30 '13 at 17:41
  • 1
    if you use condition variables (`cnd_wait()`) your locks will be extremely short which should be acceptable even for the audio render thread. I use it for the CoreMIDI readProc but I would also use it for the audio render thread. – bio Feb 19 '20 at 18:23
  • @bio I thought so too, but it turns out that any locking *at all* in the real-time context is a hazard. Event a guaranteed-never-to-block call to `pthread_trylock()` is unsafe, because anything you lock, you have to unlock again, and the unlock-mechanism can cause a system-call which in turn can blow out your time budget and cause an audio snat. (see: https://youtu.be/zrWYJ6FdOFQ?t=591 ) – Jeremy Friesner Apr 18 '21 at 06:34
  • @JeremyFriesner True. I'd prefer some lock-free queue mechanism that does not require locking but then I wouldn't know how to wait for a signal without timed busy looping. Maybe I should look into the answer below with semaphores, to see if it does any kind of locking. – bio Feb 13 '23 at 12:29
  • @bio the best I was able to come up with was to have the audio thread update an atomic timestamp (with the result of a call to `mach_absolute_time()`, which is apparently real-time-safe) when it publishes new data, and then include logic inside the non-real-time thread that looks at the values of these timestamps and uses them to estimate when is the next time it should wake up and check the shared/lock-free data structures again for more data. This sort-of works (since the real-time thread publishes new audio on a regular basis), but it's not perfect. – Jeremy Friesner Feb 13 '23 at 15:18

1 Answers1

6

Updated

A dispatch_semaphore_t works well and is more efficient than a mach semaphore_t. The original code looks like this using a dispatch semaphore:

#include <dispatch/dispatch.h>

// Declare mSemaphore somewhere it is available to multiple threads
dispatch_semaphore_t mSemaphore;


// Create the semaphore
mSemaphore = dispatch_semaphore_create(0);
// Handle error if(nullptr == mSemaphore)


// ===== RENDER THREAD
// An event happens in the render thread- set a flag and signal whoever is waiting
/*long result =*/ dispatch_semaphore_signal(mSemaphore);


// ===== OTHER THREAD
// Check the flags and act on the state change
// Wait for a signal for 2 seconds
/*long result =*/ dispatch_semaphore_wait(mSemaphore, dispatch_time(dispatch_time_now(), 2 * NSEC_PER_SEC));


// Clean up when finished
dispatch_release(mSemaphore);

Original answer:

You can use a mach semaphore_t for this purpose. I've written a C++ class that encapsulates the functionality: https://github.com/sbooth/SFBAudioEngine/blob/master/Semaphore.cpp

Whether or not you end up using my wrapper or rolling your own the code will look roughly like:

#include <mach/mach.h>
#include <mach/task.h>

// Declare mSemaphore somewhere it is available to multiple threads
semaphore_t mSemaphore;


// Create the semaphore
kern_return_t result = semaphore_create(mach_task_self(), &mSemaphore, SYNC_POLICY_FIFO, 0);
// Handle error if(result != KERN_SUCCESS)


// ===== RENDER THREAD
// An event happens in the render thread- set a flag and signal whoever is waiting
kern_return_t result = semaphore_signal(mSemaphore);
// Handle error if(result != KERN_SUCCESS)


// ===== OTHER THREAD
// Check the flags and act on the state change
// Wait for a signal for 2 seconds
mach_timespec_t duration = {
  .tv_sec = 2,
  .tv_nsec = 0
};

kern_return_t result = semaphore_timedwait(mSemaphore, duration);

// Timed out
if(KERN_OPERATION_TIMED_OUT != result)
  ;

// Handle error if(result != KERN_SUCCESS)


// Clean up when finished
kern_return_t result = semaphore_destroy(mach_task_self(), mSemaphore);
// Handle error if(result != KERN_SUCCESS)
sbooth
  • 16,646
  • 2
  • 55
  • 81
  • Thank you! This was exactly what I was looking for. I would give you a thousand in reputation if I could. I knew there would be some low level stuff I could use. Thanks a lot! PS. Will take a look at your wrapper. – Trenskow Dec 31 '13 at 00:42
  • Using a lock-free queue or circular fifo to communicate from real-time audio callbacks to the UI has been recommended by several audio developers. Polling at display frame rate (60Hz or 16.6 mS or a CADisplayLink of 1, not 100 mS) will allow updating the UI at full frame rate with the minimum number of threads. – hotpaw2 Dec 31 '13 at 20:32
  • @hotpaw2 This is definitely true and depending on the purpose even updating at the display frame rate can be excessive (for example when updating the playback time often 5 times per second is adequate). – sbooth Jan 01 '14 at 15:52