Advantages & differences#

Pigweed’s OS abstraction layers (pw_sync, pw_thread, and pw_chrono) provide type-safe C++ interfaces with robust compile-time and runtime checking. When targeting ThreadX, these APIs offer significant design and safety advantages over raw native ThreadX APIs.

Synchronization vs ThreadX#

Note

For internal design details and configuration options, see the pw_sync_threadx backend documentation.

Type-safe mutex interfaces#

Native ThreadX mutexes are represented by raw TX_MUTEX structures and handles, created via tx_mutex_create(). There is no distinction at the type level between a mutex and other types of synchronization objects.

Pigweed’s pw::sync::Mutex and pw::sync::TimedMutex are distinct, type-safe C++ classes. They enforce correct usage at compile-time and restrict invalid runtime operations:

  • Interrupt-context guardrails: Locking or unlocking a mutex within an interrupt handler causes undefined behavior or deadlock in ThreadX. Pigweed’s Mutex and TimedMutex implementations assert that they are not called in interrupt context via PW_DASSERT(!interrupt::InInterruptContext()).

  • Recursive locking prevention: Native ThreadX mutexes support recursive locking. However, Pigweed’s Mutex interface does not support recursive locking. To enforce this contract, Pigweed’s implementation asserts that the mutex is not recursively held: PW_DASSERT(backend::NotRecursivelyHeld(native_type_)), which checks that tx_mutex_ownership_count == 1.

  • Release assertions: Unlocking a native ThreadX mutex returns a status that can silently go unchecked. Pigweed’s Mutex::unlock() asserts that releasing the lock succeeded via PW_ASSERT(tx_mutex_put(&native_type_) == TX_SUCCESS).

Port-safe interrupt spinlocks#

ThreadX does not offer a native interrupt spinlock API. Developers typically attempt to implement mutual exclusion between threads and interrupts using global interrupt control flags.

Pigweed’s pw::sync::InterruptSpinLock provides a unified, port-safe spinlock implementation. It uses tx_interrupt_control(TX_INT_DISABLE) to create a critical section. Furthermore, to prevent accidental thread context switches while the InterruptSpinLock is held by a thread, Pigweed’s implementation raises the preemption threshold of the current thread to 0 (the highest priority) using tx_thread_preemption_change().

It also detects recursive locking or unlocking from an incorrect context (e.g., locking in thread context and unlocking in interrupt context, or vice-versa) via state verification checks on native_type_.state.

Warning

This backend does not support SMP yet as there’s no internal lock to spin on.

Thread notifications#

Although one may be tempted to use tx_thread_sleep and tx_thread_wait_abort to implement direct-to-thread signaling in ThreadX, this contains a race condition: if another thread or interrupt attempts to invoke tx_thread_wait_abort before the blocking thread has actually executed tx_thread_sleep, the wait abort would fail silently.

To prevent this race condition, Pigweed’s pw::sync::ThreadNotification and pw::sync::TimedThreadNotification backends for ThreadX are backed by the binary semaphore backends (pw_sync:binary_semaphore_thread_notification_backend). This ensures race-free, reliable thread signaling.

Compile-time lock safety via thread-safety annotations#

Native ThreadX mutexes are C-level structures with no integration with compile-time analysis tools. The compiler cannot verify whether a shared variable is accessed under the correct lock, nor can it enforce consistent lock acquisition orders.

Pigweed’s C++ synchronization wrappers (such as pw::sync::Mutex and pw::sync::InterruptSpinLock) integrate natively with Clang’s static thread safety analysis. By annotating members with attributes like PW_GUARDED_BY and methods with PW_EXCLUSIVE_LOCKS_REQUIRED, the compiler statically checks and warns against missing locks or improper release sequences at compile-time.

For detailed configuration requirements and lists of supported lock safety macros, refer to the pw_sync thread-safety lock annotations reference documentation.

Sleeping vs ThreadX#

Note

For details on thread options, static stack allocations, joining support, thread iteration, and snapshot integration, see the pw_thread_threadx backend documentation.

Safe sleep and yield APIs#

In native ThreadX, calling tx_thread_sleep() or tx_thread_relinquish() from an interrupt context leads to crashes or corrupts kernel state.

Pigweed’s pw::this_thread::sleep_for(), sleep_until(), and pw::thread::yield() enforce safety by asserting they are only invoked from thread contexts via PW_DCHECK(get_id() != Thread::id()).

These yield and sleep functions map directly to tx_thread_relinquish() and tx_thread_sleep() (if sleep duration is at least one tick).

Safe arbitrarily long durations#

Native ThreadX sleep and timeout functions require durations represented as ticks (ULONG). If a thread requests a sleep or synchronization timeout that exceeds the maximum value representable by ULONG, it can overflow or truncate.

Pigweed’s sleep and timed synchronization APIs automatically split long durations exceeding pw::chrono::threadx::kMaxTimeout into loop iterations under the hood, ensuring timeouts of arbitrary lengths behave correctly.

Time vs ThreadX#

Note

For configuration requirements and operational expectations (such as how frequently SystemClock::now() must be called to handle native tick overflows), see the pw_chrono_threadx backend documentation.

Non-overflowing 64-bit system clock#

ThreadX’s native tick counter (tx_time_get()) returns a ULONG (typically unsigned 32-bit). At a standard 1 kHz tick rate, a 32-bit counter wraps around in approximately 49.7 days. Failing to handle this wraparound correctly in application logic is a frequent source of long-term stability bugs.

Pigweed’s pw::chrono::SystemClock uses a thread- and interrupt-safe InterruptSpinLock to track tick overflows, expanding the tick count into a signed 64-bit value. This signed 64-bit clock will not overflow for millions of years, allowing developers to safely write standard time arithmetic without overflow concerns.

Warning

This clock backend is not compatible with TX_NO_TIMER as that disables tx_time_get().

Other functionality#

Some native ThreadX features are not wrapped by generic facades in Pigweed. In most cases, Pigweed offers modern, target-agnostic C++ alternatives that should be used instead:

Event flags (TX_EVENT_FLAGS_GROUP)#

Pigweed does not provide a multi-bit event-flag or poll facade. Instead, developers can achieve similar signaling and waiting behaviors using:

  • pw::sync::ThreadNotification: For simple thread unblocking/signaling.

  • pw::sync::BinarySemaphore / pw::sync::CountingSemaphore: For signaling and resource sharing.

  • Cooperative multitasking via pw_async2: For waiting on multiple asynchronous resources or events without blocking threads.

Queues (TX_QUEUE)#

Pigweed does not wrap ThreadX’s native C-style queue structures. Instead, developers should use Pigweed’s type-safe and allocation-free containers and utilities:

  • pw_containers: For standard queues, circular buffers, or deques.

  • pw::work_queue::WorkQueue: For offloading tasks to a thread.

  • pw::ring_buffer::Reader: For circular byte-buffers.

  • pw_rpc: For robust, structured, and cross-process/cross-device messaging.

Block & byte memory pools (TX_BLOCK_POOL / TX_BYTE_POOL)#

ThreadX’s C-style block and byte memory pool structures are not wrapped by Pigweed. Developers should prefer standard, type-safe C++ allocator interfaces and wrappers provided by:

  • pw_allocator: For type-safe, generic, and configurable memory allocation.

Repeating/periodic application timers (TX_TIMER)#

pw::chrono::SystemTimer is exclusively a one-shot timer. Pigweed does not wrap ThreadX’s native periodic application timers. Periodic timer behavior should be implemented by rescheduling the SystemTimer from within its expiry callback.