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 thattx_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 viaPW_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.