You copied the Doc URL to your clipboard.

Thread-safe initialization of Mutexes and Condition variables [ALPHA]

The Mutexes and Condition Variable parts of the porting API must adopt an initialize-on-first-use strategy. Implementations must ensure that such initializations are thread-safe.

Note

This topic describes an [ALPHA] feature. See Support level definitions.

Consider the following sample implementation of the __ARM_TPL_mutex_lock() function:

// [1] #include <platform.h>
// struct platform_mutex_t;
// platform_mutex_t *alloc_platform_mutex();
// void lock_platform_mutex(platform_mutex_t *p);
// void unlock_platform_mutex(platform_mutex_t *p);
// void destroy_platform_mutex(platform_mutex_t *p);

// [2] #include <arm-tpl.h>

// [3] implementation

void __ARM_TPL_mutex_lock(__ARM_TPL_mutex_t* __m) {
	if (__m->data == 0) {
		__m->data = static_cast<uintptr_t>(alloc_platform_mutex());
	}
	lock_platform_mutex(reinterpret_cast<platform_mutex_t*>(__m->data));
}

The anatomy of this snippet can be understood as follows:

  1. Assume the underlying system (included through platform.h) provides the type __platform_mutex_t and the functions alloc_platform_mutex(), lock_platform_mutex(), unlock_platform_mutex(), and destroy_platform_mutex().
  2. The porting API header (arm-tpl.h) is then included, which defines the type __ARM_TPL_mutex_t and the prototypes for the various porting API functions.
  3. The implementations of the various porting API functions follow.

This implementation of __ARM_TPL_mutex_lock() method leads to a race condition if multiple threads attempt to lock the same std::mutex object. Therefore, an implementation must ensure that __m->data initializes atomically. The following sections illustrate possible solutions to this problem.

Global locking

An implementation may employ a platform provided mutex to guard the initialization of *__m as follows:

static platform_mutex_t guard_mut;
				
void __ARM_TPL_mutex_lock(__ARM_TPL_mutex_t* __m) {
	volatile __ARM_TPL_mutex_t *__vm = __m;
	if (__vm->data == 0) {
		lock_platform_mutex(&guard_mut);
		if (__vm->data == 0)
			__vm->data = static_cast<uintptr_t>(alloc_platform_mutex());
		unlock_platform_mutex(&guard_mut);
	}
	lock_platform_mutex(static_cast<platform_mutex_t*>(*__vm));
}

Note

This solution could result in reduced performance because threads must contend for the shared mutex guard_mut for each initial std::mutex lock operation.

Lock free

An implementation avoiding global locking is achievable using the lock-free concurrency constructs available through the <stdatomic.h> header. The following snippet atomically attempts to initialize __m->data, and undo its attempt if another thread has already done the initialization:

#include <cstdint>
#include <stdatomic.h>

int __ARM_TPL_mutex_lock(__ARM_TPL_mutex_t *__m) {
	if (__m->data == 0){
		uintptr_t mut_new = reinterpret_cast<uintptr_t>(alloc_platform_mutex());
		uintptr_t mut_null = 0;
		if (!atomic_compare_exchange_strong(&__m->data, &mut_null, mut_new))
			destroy_platform_mutex(reinterpret_cast<platform_mutex_t*>(mut_new));
	}
	return lock_platform_mutex(reinterpret_cast<platform_mutex_t*>(__m->data));
}

The ARMv6-M architecture does not support this method.

Was this page helpful? Yes No