Pthread
POSIX Threads is abbreviated as Pthreads. POSIX is the abbreviation of "Portable Operating System Interface". POSIX is a set of standards developed by the IEEE Computer Society to improve the compatibility of different operating systems and the portability of applications. Pthreads is the POSIX standard for threads, which is defined in the POSIX.1c, Threads extensions (IEEE Std1003.1c-1995) standard, which defines a set of types, functions and constants for the C programming language. There are about 100 APIs defined in the pthread.h header file and a thread library. All APIs have the "pthread_" prefix and can be divided into 4 categories:
Thread management: includes functions for creating, detaching, joining, setting and querying thread properties, etc.
Mutex: Abbreviation of "mutual exclusion", used to restrict thread access to shared data and protect the integrity of shared data. Includes functions for creating, destroying, locking and unlocking mutexes and some functions for setting or modifying mutex properties.
Condition variable: used for communication between threads that share a mutex. It includes functions for creating, destroying, waiting, and sending signals for condition variables.
Read/write locks and barriers: including functions for creating, destroying, waiting, and setting related properties of read/write locks and barriers.
POSIX semaphores are used with Pthreads, but are not part of the Pthreads standard. They are defined in the POSIX.1b, Real-time extensions (IEEE Std1003.1b-1993) standard. Therefore, the prefix of semaphore-related functions is "sem_" instead of "pthread_".
Message queues, like semaphores, are used with Pthreads and are not part of the Pthreads standard definition. They are defined in the IEEE Std 1003.1-2001 standard. The prefix of message queue related functions is "mq_".
Function prefix | Function Group |
| The thread itself and various related functions |
| Thread attributes object |
| Mutex |
| Mutex attributes object |
| Condition variables |
| Condition variable properties object |
| Read-write lock |
| Read-write lock attribute object |
| Spin lock |
| barrier |
| Barrier Properties Object |
| Semaphore |
| Message Queues |
Most Pthreads functions return a value of 0 if they are executed successfully, and an error code included in the header file if they are not executed successfully errno.h
. Many operating systems support Pthreads, such as Linux, MacOSX, Android, and Solaris, so applications written using Pthreads functions have good portability and can be directly compiled and run on many platforms that support Pthreads.
The POSIX API interface used in RT-Thread includes several parts: libc (such as newlib), filesystem, pthread, etc.
Enable pthread in menuconfig and set the maximum number of pthreads supported
In user code, you can use
pthread.h, sche.h
the header files provided by pthread to program
RT-Thread implements most of the functions and constants of Pthreads, which are defined in the pthread.h, mqueue.h, semaphore.h and sched.h header files according to the POSIX standard. Pthreads is a sub-library of libc. Pthreads in RT-Thread is a wrapper based on RT-Thread kernel functions to make it compliant with the POSIX standard. The following chapters will introduce the Pthreads functions and related functions implemented in RT-Thread in detail.
pthread_t is a redefinition of the rt_thread_t type, defined in the pthread.h header file. rt_thread_t is the thread handle (or thread identifier) of RT-Thread, which is a pointer to the thread control block. Before creating a thread, you need to define a variable of type pthread_t. Each thread corresponds to its own thread control block. The thread control block is a data structure used by the operating system to control threads. It stores some thread information, such as priority, thread name, and thread stack address. The thread control block and thread specific information are described in detail in the Thread Scheduling and Management chapter of the RT-Thread Programming Manual.
parameter | describe |
tid | Pointer to the thread handle (thread identifier), cannot be NULL |
attr | A pointer to the thread attributes. If NULL is used, the default thread attributes are used. |
start | Thread entry function address |
arg | Parameters passed to the thread entry function |
return | —— |
0 | success |
EINVAL | Invalid parameter |
ENOMEM | Failed to allocate memory dynamically |
This function creates a pthread thread. This function will dynamically allocate POSIX thread data blocks and RT-Thread thread control blocks, and save the starting address of the thread control block (thread ID) in the memory pointed to by the parameter tid. This thread identifier can be used to operate this thread in other threads; and save the thread attributes pointed to by attr, the thread entry function pointed to by start, and the entry function parameter arg in the thread data block and thread control block. If the thread is created successfully, the thread immediately enters the ready state and participates in the system scheduling. If the thread creation fails, the resources previously occupied by the thread will be released.
Thread properties and related functions are introduced in detail in the chapter on advanced thread programming. In general, the default properties can be used.
Note
Note: After creating a pthread, if the thread needs to be created and used repeatedly, you need to set the pthread to detach mode, or use pthread_join to wait for the created pthread to end.
The following program will initialize 2 threads, they have a common entry function, but their entry parameters are different. Otherwise, they have the same priority and are scheduled in round-robin fashion using time slices.
parameter | describe |
thread | Thread handle (thread identifier) |
return | —— |
0 | success |
When this function is called, if the pthread thread has not ended, the detached state of the thread thread attribute will be set to detached; when the thread thread has ended, the system will reclaim the resources occupied by the pthread thread.
Usage: The child thread calls pthread_detach(pthread_self()) (pthread_self() returns the thread handle of the current calling thread), or other threads call pthread_detach(thread_id). The detached state of thread attributes will be described in detail later.
Note
Note: Once the thread attribute's detached state is set to detached, the thread cannot be waited for by the pthread_join() function or set to detached again.
The following program will initialize 2 threads with the same priority and schedule them in round-robin mode. Both threads will be set to the detached state, and will automatically exit after printing information 3 times in a loop. After exiting, the system will automatically recycle its resources.
parameter | describe |
thread | Thread handle (thread identifier) |
value_ptr | User-defined pointer used to store the return value address of the waiting thread, which can be obtained by the function pthread_join() |
return | —— |
0 | success |
EDEADLK | Thread joins itself |
EINVAL | Join a thread in detached state |
ESRCH | thread not found |
This function will make the calling thread wait in a blocking manner for the thread whose thread detachment attribute is joinable to finish running, and obtain the return value of thread. The address of the return value is saved in value_ptr, and the resources occupied by thread are released.
The functions of pthread_join() and pthread_detach() are similar, and are used to reclaim the resources occupied by the thread after the thread ends. A thread cannot wait for itself to end. The detachment state of the thread must be joinable, and one thread only corresponds to one pthread_join()
call. The resources occupied by a thread with a detachment state of joinable will be released only when other threads execute it pthread_join()
. Therefore, in order to avoid memory leaks, all threads that will end their operation must either have their detachment state set to detached or use pthread_join() to reclaim their occupied resources.
Waiting for the thread to end sample code
The following program code will initialize 2 threads with the same priority. Threads with the same priority are scheduled in round-robin fashion according to the time slice. The separation state of the 2 thread attributes is the default value of joinable. Thread 1 starts running first and ends after printing information three times in a loop. Thread 2 calls pthread_join() to block and wait for thread 1 to end, and reclaim the resources occupied by thread 1. Then thread 2 prints information every 2 seconds.
parameter | describe |
value_ptr | User-defined pointer used to store the return value address of the waiting thread, which can be obtained by the function pthread_join() |
When a pthread thread calls this function, it terminates execution, just like a process calling the exit() function, and returns a pointer to the thread's return value. Thread exit is initiated by the thread itself.
Note
Note: If the thread's detached state is joinable, the resources occupied by the thread will not be released after the thread exits. The pthread_join() function must be called to release the resources occupied by the thread.
This program will initialize two threads with the same priority. Threads with the same priority are scheduled in round-robin fashion according to the time slice. The separation state of the two thread attributes is the default value of joinable. Thread 1 starts running first, prints a message and then sleeps for 2 seconds, then prints the exit message and ends. Thread 2 calls pthread_join() to block and wait for thread 1 to end, and reclaims the resources occupied by thread 1. Then thread 2 prints a message every 2 seconds.
Mutex locks, also known as mutually exclusive semaphores, are a special type of binary semaphore. Mutex locks are used to ensure the integrity of shared resources, ensuring that only one thread can access the shared resource at any one time. To access a shared resource, a thread must first obtain a mutex lock, and then release the mutex lock after the access is completed. Embedded shared resources include memory, IO, SCI, SPI, etc. If two threads access a shared resource at the same time, problems may occur because one thread may use the resource while another thread is modifying the shared resource and think that the shared resource has not changed.
There are only two operations for mutexes: locking or unlocking. Only one thread can hold a mutex at a time. When a thread holds it, the mutex is in a locked state, and this thread obtains its ownership. On the contrary, when this thread releases it, the mutex will be unlocked and lose its ownership. When a thread holds a mutex, other threads will not be able to unlock it or hold it.
The main operations on a mutex include: calling pthread_mutex_init() to initialize a mutex, calling pthread_mutex_destroy() to destroy a mutex, calling pthread_mutex_lock() to lock a mutex, and calling pthread_mutex_unlock() to unlock a mutex.
A potential problem caused by using a mutex is thread priority flipping. The priority inheritance algorithm is implemented in the RT-Thread operating system. Priority inheritance means raising the priority of a low-priority thread that occupies a certain resource to the same priority as the highest priority thread among all threads waiting for the resource, and then executing it. When the low-priority thread releases the resource, the priority returns to the initial setting. Therefore, threads that inherit priority prevent system resources from being preempted by any intermediate priority thread.
For a detailed introduction to priority inversion, please refer to the Mutex section of "Inter-thread Synchronization" .
Each mutex lock corresponds to a mutex lock control block, which contains some information about the control of the mutex lock. Before creating a mutex lock, you must first define a variable of type pthread_mutex_t. pthread_mutex_t is a redefinition of pthread_mutex. The pthread_mutex data structure is defined in the pthread.h header file. The data structure is as follows:
parameter | describe |
mutex | Mutex handle, cannot be NULL |
attr | A pointer to the mutex attributes. If the pointer is NULL, the default attributes are used. |
return | —— |
0 | success |
EINVAL | Invalid parameter |
This function initializes the mutex mutex and sets the mutex attributes according to the mutex attribute object pointed to by attr. After successful initialization, the mutex is in an unlocked state and can be acquired by threads. This function is a wrapper of the rt_mutex_init() function.
In addition to calling the pthread_mutex_init() function to create a mutex, you can also use the macro PTHREAD_MUTEX_INITIALIZER to statically initialize the mutex. The method is: pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
(structure constant), which is equivalent to calling pthread_mutex_init() with attr specified as NULL.
The mutex lock properties and related functions will be introduced in detail in the chapter on advanced thread programming. In general, the default properties can be used.
parameter | describe |
mutex | Mutex handle, cannot be NULL |
return | —— |
0 | success |
EINVAL | mutex is empty or mutex has been destroyed |
EBUSY | Mutex is in use |
This function destroys the mutex. After destruction, the mutex is in an uninitialized state. After destruction, the mutex's attributes and control block parameters are no longer valid, but you can call pthread_mutex_init() to reinitialize the destroyed mutex. However, there is no need to destroy a mutex that is statically initialized using the macro PTHREAD_MUTEX_INITIALIZER.
The mutex can be destroyed only when it is determined that the mutex is not locked and no thread is blocked on the mutex.
parameter | describe |
mutex | Mutex handle, cannot be NULL |
return | —— |
0 | success |
EINVAL | Invalid parameter |
EDEADLK | The thread calls this function repeatedly when the mutex is not a nested lock. |
This function locks the mutex mutex. This function is a wrapper of the rt_mutex_take() function. If the mutex mutex has not been locked, the thread that applies for the mutex will successfully lock the mutex. If the mutex mutex has been locked by the current thread and the mutex type is a nested lock, the mutex holding count will increase by 1, and the current thread will not hang and wait (deadlock), but the thread must unlock it the same number of times. If the mutex mutex is locked and held by other threads, the current thread will be blocked until other threads unlock the mutex. The thread waiting for the mutex will obtain the mutex according to the first-in-first-out principle.
parameter | describe |
mutex | Mutex handle, cannot be NULL |
return | —— |
0 | success |
EINVAL | Invalid parameter |
EDEADLK | The thread calls this function repeatedly when the mutex is not a nested lock. |
EBUSY | The mutex has been locked by another thread |
This function is a non-blocking version of the pthread_mutex_lock() function. The difference is that if the mutex is already locked, the thread will not be blocked, but will immediately return an error code.
parameter | describe |
mutex | Mutex handle, cannot be NULL |
return | —— |
0 | success |
EINVAL | Invalid parameter |
EPERM | The thread calls this function repeatedly when the mutex is not a nested lock. |
EBUSY | Unlocks a mutex of type error-checking lock held by another thread |
Calling this function to unlock the mutex is a wrapper of the rt_mutex_release() function. When a thread completes access to shared resources, it should release the occupied mutex as soon as possible so that other threads can acquire the mutex in time. Only the thread that already owns the mutex can release it. Each time the mutex is released, its holding count decreases by 1. When the holding count of the mutex is zero (that is, the holding thread has released all holding operations), the mutex becomes available, and the threads waiting on the mutex will be awakened in a first-in-first-out manner. If the running priority of a thread is raised by the mutex, then when the mutex is released, the thread returns to the priority before holding the mutex.
This program will initialize 2 threads with the same priority. Both threads will call the same printer() function to output their own strings. The printer() function will only output one character at a time, and then sleep for 1 second. The thread that calls the printer() function will also sleep. If the mutex is not used, thread 1 prints a character, and then executes thread 2 after sleeping. Thread 2 prints a character. In this way, the strings of thread 1 and thread 2 cannot be printed completely, and the printed strings are messy. If a mutex is used to protect the printer() function shared by two threads, thread 1 will execute the printer() printing function after getting the mutex to print a character, and then sleep for 1 second. This is to switch to thread 2. Because the mutex has been locked by thread 1, thread 2 will be blocked until thread 1 actively releases the mutex after printing the string completely, and thread 2 will be awakened.
A condition variable is actually a semaphore used for synchronization between threads. A condition variable is used to block a thread. When the condition is met, a condition is sent to the blocked thread, and the blocked thread is awakened. The condition variable needs to be used in conjunction with a mutex lock, which is used to protect shared data.
Condition variables can be used to notify the status of shared data. For example, if a thread processing a shared resource queue finds that the queue is empty, the thread can only wait until a node is added to the queue, and then send a condition variable signal to activate the waiting thread.
The main operations of condition variables include: calling pthread_cond_init() to initialize the condition variable, calling pthread_cond_destroy() to destroy a condition variable, calling pthread_cond_wait() to wait for a condition variable, and calling pthread_cond_signal() to send a condition variable.
Each condition variable corresponds to a condition variable control block, which includes some information about the operation of the condition variable. Before initializing a condition variable, you need to define a pthread_cond_t condition variable control block. pthread_cond_t is a redefinition of the pthread_cond structure type, which is defined in the pthread.h header file.
parameter | describe |
cond | Condition variable handle, cannot be NULL |
attr | Pointer to the condition variable attributes. If NULL, the default attribute value is used. |
return | —— |
0 | success |
EINVAL | Invalid parameter |
This function initializes the cond condition variable and sets its attributes according to the condition variable attributes pointed to by attr. This function is a wrapper of the rt_sem_init() function and is implemented based on semaphores. After successful initialization, the condition variable is in an unavailable state.
You can also use the macro PTHREAD_COND_INITIALIZER to statically initialize a condition variable: pthread_cond_t cond = PTHREAD_COND_INITIALIZER
(structure constant), which is equivalent to calling pthread_cond_init() with attr specified as NULL.
Attr is usually set to NULL to use the default value, which will be introduced in the chapter on advanced thread programming.
parameter | describe |
cond | Condition variable handle, cannot be NULL |
return | —— |
0 | success |
EINVAL | Invalid parameter |
EPERM | The thread calls this function repeatedly when the mutex is not a nested lock. |
EBUSY | Condition variable is being used |
This function will destroy the cond condition variable, and cond will be in an uninitialized state after destruction. After destruction, the attributes and control block parameters of the condition variable will no longer be valid, but you can call pthread_cond_init() or statically reinitialize it.
Before destroying a condition variable, you need to make sure that no thread is blocked on the condition variable and is not waiting to acquire, signal, or broadcast.
parameter | describe |
cond | Condition variable handle, cannot be NULL |
mutex | Pointer to the mutex control block, cannot be NULL |
return | —— |
0 | success |
EINVAL | Invalid parameter |
This function will get the cond condition variable in a blocking manner. The thread needs to lock the mutex before waiting for the condition variable. This function first determines whether the condition variable is available. If it is not available, it will initialize a condition variable, then unlock the mutex, and then try to get a semaphore. When the semaphore value is greater than zero, it means that the semaphore is available. The thread will get the semaphore and the condition variable, and the corresponding semaphore value will be reduced by 1. If the semaphore value is equal to zero, it means that the semaphore is not available. The thread will block until the semaphore is available, and then the mutex will be locked again.
parameter | describe |
cond | Condition variable handle, cannot be NULL |
mutex | Pointer to the mutex control block, cannot be NULL |
abstime | The specified waiting time is in OS ticks. |
return | —— |
0 | success |
EINVAL | Invalid parameter |
EPERM | The thread calls this function repeatedly when the mutex is not a nested lock. |
ETIMEDOUT | time out |
The only difference between this function and the pthread_cond_wait() function is that if the condition variable is not available, the thread will be blocked for abstime. After the timeout, the function will directly return the ETIMEDOUT error code and the thread will be awakened and enter the ready state.
parameter | describe |
cond | Condition variable handle, cannot be NULL |
return | —— |
0 | success |
This function will send a signal and wake up only one thread waiting for the cond condition variable. It is a wrapper of the rt_sem_release() function, that is, sending a semaphore. When the value of the semaphore is equal to zero and there is a thread waiting for this semaphore, the first thread in the thread queue waiting for the semaphore will be woken up and it will obtain the semaphore. Otherwise, the value of the semaphore will be increased by 1.
parameter | describe |
cond | Condition variable handle, cannot be NULL |
return | —— |
0 | success |
EINVAL | Invalid parameter |
Calling this function will wake up all threads waiting on the cond condition variable.
This program is a producer-consumer model, with a producer thread and a consumer thread, which have the same priority. The producer will produce a number every 2 seconds, put it in the linked list pointed to by head, and then call pthread_cond_signal() to send a signal to the consumer thread, notifying the consumer thread that there is data in the linked list. The consumer thread will call pthread_cond_wait() to wait for the producer thread to send a signal.
Read-write locks are also called multi-reader single-writer locks. Read-write locks divide access to shared resources into readers and writers. Readers only read shared resources, while writers need to write to shared resources. Only one thread can occupy a read-write lock in write mode at the same time, but multiple threads can occupy a read-write lock in read mode at the same time. Read-write locks are suitable for situations where the number of reads on a data structure is much greater than the number of writes, because read mode locking can be shared, while write mode locking means exclusive.
Read-write locks are usually implemented based on mutex locks and condition variables. A thread can read and write a read-write lock multiple times, and must also unlock it a corresponding number of times.
The main operations of the read-write lock include: calling pthread_rwlock_init() to initialize a read-write lock, the writer thread calls pthread_rwlock_wrlock() to write-lock the read-write lock, the reader thread calls pthread_rwlock_rdlock() to read-lock the read-write lock, and when the read-write lock is no longer needed, pthread_rwlock_destroy() is called to destroy the read-write lock.
Each read-write lock corresponds to a read-write lock control block, which includes some information about the operations on the read-write lock. pthread_rwlock_t is a redefinition of the pthread_rwlock data structure, defined in the pthread.h header file. Before creating a read-write lock, you need to define a data structure of type pthread_rwlock_t.
parameter | describe |
rwlock | Read-write lock handle, cannot be NULL |
attr | Pointer to the read-write lock attribute. RT-Thread does not use this variable. |
return | —— |
0 | success |
EINVAL | Invalid parameter |
This function initializes an rwlock read-write lock. This function uses default values to initialize the semaphore and condition variables of the read-write lock control block, and the related count parameters are initially set to 0. The initialized read-write lock is in an unlocked state.
You can also use the macro PTHREAD_RWLOCK_INITIALIZER to statically initialize the read-write lock, the method is: pthread_rwlock_t mutex = PTHREAD_RWLOCK_INITIALIZER (structure constant), which is equivalent to calling pthread_rwlock_init() when attr is specified as NULL.
Attr is usually set to NULL to use the default value, which will be introduced in the chapter on advanced thread programming.
parameter | describe |
rwlock | Read-write lock handle, cannot be NULL |
return | —— |
0 | success |
EINVAL | Invalid parameter |
EBUSY | The read-write lock is currently being used or there is a thread waiting for the read-write lock |
EDEADLK | Deadlock |
This function destroys an rwlock read-write lock, and correspondingly destroys the mutex and condition variable in the read-write lock. After the destruction, the attributes and control block parameters of the read-write lock will no longer be valid, but you can call pthread_rwlock_init() or statically reinitialize the read-write lock.
Blocking mode reads the read-write lock
parameter | describe |
rwlock | Read-write lock handle, cannot be NULL |
return | —— |
0 | success |
EINVAL | Invalid parameter |
EDEADLK | Deadlock |
The reader thread can call this function to read-lock the rwlock read-write lock. If the read-write lock is not write-locked and no writer thread is blocked on the read-write lock, the read-write thread will successfully acquire the read-write lock. If the read-write lock is already write-locked, the reader thread will be blocked until the thread that write-locked the read-write lock unlocks it.
Non-blocking way to read the read-write lock
parameter | describe |
rwlock | Read-write lock handle, cannot be NULL |
return | —— |
0 | success |
EINVAL | Invalid parameter |
EBUSY | The read-write lock is currently being used or there is a thread waiting for the read-write lock |
EDEADLK | Deadlock |
The difference between this function and the pthread_rwlock_rdlock() function is that if the read-write lock is already write-locked, the reader thread will not be blocked, but an error code EBUSY will be returned.
Specify the blocking time to read the read-write lock
parameter | describe |
rwlock | Read-write lock handle, cannot be NULL |
abstime | The specified waiting time is in OS ticks. |
return | —— |
0 | success |
EINVAL | Invalid parameter |
ETIMEDOUT | time out |
EDEADLK | Deadlock |
The difference between this function and the pthread_rwlock_rdlock() function is that if the read-write lock has been write-locked, the reader thread will block for the specified abstime duration. After the timeout, the function will return the error code ETIMEDOUT and the thread will be awakened and enter the ready state.
Blocking mode write lock to read-write lock
parameter | describe |
rwlock | Read-write lock handle, cannot be NULL |
return | —— |
0 | success |
EINVAL | Invalid parameter |
EDEADLK | Deadlock |
The writer thread calls this function to write-lock the rwlock read-write lock. The write-lock read-write lock is similar to a mutex. Only one thread can write-lock the read-write lock at the same time. If no thread has locked the read-write lock, that is, the read-write lock value is 0, the writer thread that calls this function will write-lock the read-write lock, and other threads cannot obtain the read-write lock at this time. If a thread has already locked the read-write lock, that is, the read-write lock value is not 0, the writer thread will be blocked until the read-write lock is unlocked.
Non-blocking write lock read-write lock
parameter | describe |
rwlock | Read-write lock handle, cannot be NULL |
return | —— |
0 | success |
EINVAL | Invalid parameter |
EBUSY | The read-write lock is currently locked by a write thread or a writing thread is blocked on the read-write lock |
EDEADLK | Deadlock |
The only difference between this function and the pthread_rwlock_wrlock() function is that if a thread has already locked the read-write lock, that is, the read-write lock value is not 0, the writer thread that calls this function will directly return an error code and the thread will not be blocked.
Specify the blocking time to lock the read-write lock
parameter | describe |
rwlock abstime | Read-write lock handle, cannot be NULL. Specified waiting time, in OS ticks. |
return | —— |
0 | success |
EINVAL | Invalid parameter |
ETIMEDOUT | time out |
EDEADLK | Deadlock |
The only difference between this function and the pthread_rwlock_wrlock() function is that if a thread has already locked the read-write lock, that is, the read-write lock value is not 0, the calling thread blocks for the specified abstime duration. After the timeout, the function will return the error code ETIMEDOUT and the thread will be awakened and enter the ready state.
parameter | describe |
rwlock | Read-write lock handle, cannot be NULL |
return | —— |
0 | success |
EINVAL | Invalid parameter |
EDEADLK | Deadlock |
This function can unlock the rwlock read-write lock. If a thread locks the same read-write lock multiple times, it must unlock it the same number of times. If there are multiple threads waiting to lock the read-write lock after unlocking, the system will activate the waiting threads according to the first-in-first-out rule.
This program has two reader threads and one writer thread. The two reader threads first read the read-write lock, then sleep for 2 seconds. This means that other reader threads can still read the read-write lock and read the shared data.
Barrier is a method of multi-thread synchronization. Barrier means barrier or railing, which blocks multiple threads that arrive in succession in front of the same railing until all threads arrive, and then removes the railing and lets them pass at the same time. The threads that arrive first will be blocked, and after all threads that call the pthread_barrier_wait() function (the number is equal to the count specified when the barrier is initialized) have arrived, these threads will enter the ready state from the blocked state and participate in system scheduling again.
The barrier is implemented based on condition variables and mutexes. The main operations include: calling pthread_barrier_init() to initialize a barrier, other threads calling pthread_barrier_wait(), the thread wakes up and enters the ready state after all threads expire, and calling pthread_barrier_destroy() to destroy a barrier when the barrier is no longer in use.
Before creating a barrier, you need to define a pthread_barrier_t barrier control block. pthread_barrier_t is a redefinition of the pthread_barrier structure type, defined in the pthread.h header file.
parameter | describe |
attr | Pointer to the barrier attribute. If NULL is passed, the default value is used. If it is not NULL, PTHREAD_PROCESS_PRIVATE must be used. |
barrier | Barrier Handle |
count | The specified number of waiting threads |
return | —— |
0 | success |
EINVAL | Invalid parameter |
This function creates a barrier and initializes the condition variables and mutex locks of the barrier control block according to the default parameters. After initialization, the number of waiting threads specified is count, and pthread_barrier_wait() must be called for count threads.
Attr is usually set to NULL to use the default value, which will be introduced in the chapter on advanced thread programming.
parameter | describe |
barrier | Barrier Handle |
return | —— |
0 | success |
EINVAL | Invalid parameter |
This function destroys a barrier. After the barrier is destroyed, its attributes and control block parameters will no longer be valid, but can be reinitialized by calling pthread_barrier_init().
parameter | describe |
barrier | Barrier Handle |
return | —— |
0 | success |
EINVAL | Invalid parameter |
This function synchronizes the threads waiting in front of the barrier. It is called actively by each thread. If the number of threads waiting in front of the barrier is not 0, the count will be reduced by 1. If the count is 0 after reduction, it means that all threads have reached the barrier. All threads that have arrived will be awakened and re-enter the ready state to participate in system scheduling. If the count is not 0 after reduction, it means that there are still threads that have not reached the barrier. The calling thread will be blocked until all threads reach the barrier.
This program will create three threads, initialize a barrier, and initialize the number of threads waiting for the barrier to 3. All three threads will call pthread_barrier_wait() to wait in front of the barrier. When all three threads arrive, the three threads will enter the ready state, and then print out the count information every 2 seconds.
This chapter provides a detailed introduction to some rarely used attribute objects and related functions.
The thread attributes implemented by RT-Thread include thread stack size, thread priority, thread separation status, and thread scheduling policy. The attribute object must be initialized before using pthread_create(). API functions such as setting thread attributes should be called before creating a thread. Changes to thread attributes will not affect already created threads.
The thread attribute structure pthread_attr_t is defined in the pthread.h header file. The thread attribute structure is as follows:
Thread attribute initialization and deinitialization
The thread attribute initialization and deinitialization functions are as follows:
parameter | describe |
attr | Pointer to thread attributes |
return | —— |
0 | success |
Using the pthread_attr_init() function will initialize the thread attribute structure attr with default values, which is equivalent to setting this parameter to NULL when calling the thread initialization function. A pthread_attr_t attribute object needs to be defined before use. This function must be called before the pthread_create() function.
The pthread_attr_destroy() function deinitializes the attribute pointed to by attr, and then the pthread_attr_init() function can be called again to reinitialize the attribute object.
Set the detached state of a thread/get the detached state of a thread as shown below. By default, the thread is in a non-detached state.
parameter | describe |
attr | Pointer to thread attributes |
state | Thread detached state |
return | —— |
0 | success |
The thread detach state attribute value state can be PTHREAD_CREATE_JOINABL (non-detached) and PTHREAD_CREATE_DETACHED (detached).
The detached state of a thread determines how a thread reclaims the resources it occupies after it ends. There are two detached states for a thread: joinable or detached. When a thread is created, pthread_join() or pthread_detach() should be called to reclaim the resources occupied by the thread after it ends. If the detached state of a thread is joinable, other threads can call the pthread_join() function to wait for the thread to end and obtain the thread return value, and then reclaim the resources occupied by the thread. A thread in the detached state cannot be joined by other threads, and releases system resources immediately after it ends.
The function to set and get the thread scheduling policy is as follows:
Only the function interface is implemented. By default, different priorities are scheduled based on priority, and the same priority time slice is polled and scheduled.
The function to set thread priority/get thread priority is as follows:
parameter | describe |
attr | Pointer to thread attributes |
param | Pointer to the scheduling parameters |
return | —— |
0 | success |
The pthread_attr_setschedparam() function sets the priority of a thread. Use param to assign a value to the thread attribute priority.
The parameter struct sched_param is defined in sched.h and has the following structure:
The sched_priority member of the sched_param structure controls the priority value of the thread.
The functions to set/get the thread's stack size are as follows:
parameter | describe |
attr | Pointer to thread attributes |
stack_size | Thread stack size |
return | —— |
0 | success |
The pthread_attr_setstacksize() function can set the stack size in bytes. In most systems, stack space address alignment is required (for example, 4-byte address alignment is required in the ARM architecture).
The functions for setting/getting the thread's stack address and stack size are as follows:
parameter | describe |
attr | Pointer to thread attributes |
stack_size | Thread stack size |
stack_base | Thread stack address |
return | —— |
0 | success |
Thread attribute related functions
The functions for setting/getting the scope of a thread are as follows:
parameter | describe |
attr | Pointer to thread attributes |
scope | Thread Scope |
return | —— |
0 | scope is PTHREAD_SCOPE_SYSTEM |
EOPNOTSUPP | scope is PTHREAD_SCOPE_PROCESS |
EINVAL | scope is PTHREAD_SCOPE_SYSTEM |
This program will initialize 2 threads. They have the same entry function, but their entry parameters are different. The first thread created will use the provided attr thread attributes, and the other thread will use the system default attributes. The thread priority is an important parameter, so this program will modify the priority of the first created thread to 8, while the system default priority is 24.
Cancellation is a mechanism that allows one thread to terminate another thread's execution. A thread can send a cancellation request to another thread. Depending on the settings, the target thread may ignore it, terminate it immediately, or postpone it until the next cancellation point.
You can send a cancellation request using the following function:
parameter | describe |
thread | Thread handle |
return | —— |
0 | success |
This function sends a cancellation request to thread. Whether and when Thread responds to the cancellation request depends on the state and type of thread cancellation.
You can use the following function to set a cancellation request:
parameter | describe |
state | There are two values: PTHREAD_CANCEL_ENABLE: cancel enable PTHREAD_CANCEL_DISABLE: cancel disable (the default value when the thread is created) |
oldstate | Save the original cancellation status |
return | —— |
0 | success |
EINVAL | state is not PTHREAD_CANCEL_ENABLE or PTHREAD_CANCEL_DISABLE |
This function sets the cancellation state and is called by the thread itself. A thread that is cancellation-enabled will respond to a cancellation request, while a thread that is not cancellation-enabled will not respond to a cancellation request.
The cancellation type can be set using the following function, which is called by the thread itself:
parameter | describe |
type | There are two values: PTHREAD_CANCEL_DEFFERED: After receiving the cancellation request, the thread continues to run to the next cancellation point and then ends. (The default value when the thread is created) PTHREAD_CANCEL_ASYNCHRONOUS: The thread ends immediately. |
oldtype | Save original cancellation type |
return | —— |
0 | success |
EINVAL | state is not PTHREAD_CANCEL_DEFFERED or PTHREAD_CANCEL_ASYNCHRONOUS |
You can use the following function to set the cancellation point:
This function creates a cancellation point at the thread where it is called. Primarily called by threads that do not have cancellation points that can respond to cancellation requests. If pthread_testcancel() is called with cancellation disabled, this function has no effect.
The cancellation point is where the thread ends its execution after accepting the cancellation request. According to the POSIX standard, system calls that cause blocking, such as pthread_join(), pthread_testcancel(), pthread_cond_wait(), pthread_cond_timedwait(), and sem_wait(), are all cancellation points.
All cancellation points included in RT-Thread are as follows:
mq_receive()
mq_send()
mq_timedreceive()
mq_timedsend()
msgrcv()
msgsnd()
msync()
pthread_cond_timedwait()
pthread_cond_wait()
pthread_join()
pthread_testcancel()
sem_timedwait()
sem_wait()
pthread_rwlock_rdlock()
pthread_rwlock_timedrdlock()
pthread_rwlock_timedwrlock()
pthread_rwlock_wrlock()
Thread Cancellation Example Code
This program will create two threads. Thread 2 will sleep for 8 seconds immediately after it starts running. Thread 1 will set its own cancellation status and type, and then print the running count information in an infinite loop. After waking up, thread 2 will send a cancellation request to thread 1, and thread 1 will stop running immediately after receiving the cancellation request.
You can use the following function to initialize it once:
parameter | describe |
once_control | Control variables |
init_routine | Execute function |
return | —— |
0 | success |
Sometimes we need to initialize some variables only once. If we initialize them more than once, the program will fail. In traditional sequential programming, one-time initialization is often managed by using Boolean variables. The control variable is statically initialized to 0, and any code that depends on the initialization can test the variable. If the variable value is still 0, it can perform the initialization and then set the variable to 1. The code that checks later will skip the initialization.
The thread cleanup function interface is as follows:
parameter | describe |
execute | 0 or 1, determines whether to execute the cleanup function |
routine | Pointer to the cleanup function |
arg | Parameters passed to the cleanup function |
pthread_cleanup_push() puts the specified cleanup function routine into the thread's cleanup function linked list. pthread_cleanup_pop() takes the first function from the head of the cleanup function linked list. If execute is a non-zero value, this function is executed.
Determine whether two threads are equal
parameter | describe |
pthread_t | Thread handle |
return | —— |
0 | Not equal |
1 | equal |
pthread_self() returns the handle of the calling thread.
Get the maximum and minimum priorities
parameter | describe |
policy | 2 values are available: SCHED_FIFO, SCHED_RR |
The return value of sched_get_priority_min() is 0, which is the maximum priority in RT-Thread, and the return value of sched_get_priority_max() is the minimum priority.
The mutex properties implemented by RT-Thread include mutex type and mutex scope.
Mutex lock attribute initialization and deinitialization
parameter | describe |
attr | Pointer to the mutex attributes object |
return | —— |
0 | success |
EINVAL | Invalid parameter |
The pthread_mutexattr_init() function will initialize the attribute object pointed to by attr with default values, which is equivalent to setting the attribute parameter to NULL when calling the pthread_mutex_init() function.
The pthread_mutexattr_destroy() function will deinitialize the attribute object pointed to by attr, and then the pthread_mutexattr_init() function can be called to reinitialize it.
parameter | describe |
type | Mutex Type |
pshared | There are 2 optional values: PTHREAD_PROCESS_PRIVATE: The default value, used to synchronize threads in this process only. PTHREAD_PROCESS_SHARED: Used to synchronize threads in this process and other processes. |
return | —— |
0 | success |
EINVAL | Invalid parameter |
parameter | describe |
type | Mutex Type |
attr | Pointer to the mutex attributes object |
return | —— |
0 | success |
EINVAL | Invalid parameter |
The type of mutex determines how a thread behaves when acquiring a mutex. RT-Thread implements three types of mutexes:
PTHREAD_MUTEX_NORMAL: Normal lock. When a thread locks, the other threads requesting the lock will form a waiting queue and obtain the lock in a first-in-first-out manner after unlocking. If a thread tries to reacquire the mutex without first releasing the mutex, it will not cause a deadlock, but return an error code, just like the error detection lock.
PTHREAD_MUTEX_RECURSIVE: Nested locks allow a thread to successfully acquire the same lock multiple times, requiring the same number of unlocks to release the mutex.
PTHREAD_MUTEX_ERRORCHECK: Error-checking lock, returns an error if a thread tries to reacquire the mutex without first unlocking it. This ensures that deadlock does not occur when multiple locks are not allowed.
Use the default value PTHREAD_PROCESS_PRIVATE to initialize the condition variable attribute attr. You can use the following function:
parameter | describe |
attr | Pointer to the condition variable attributes object |
return | —— |
0 | success |
EINVAL | Invalid parameter |
Get the scope of a condition variable
parameter | describe |
attr | Pointer to the condition variable attributes object |
return | —— |
0 | success |
EINVAL | Invalid parameter |
parameter | describe |
attr | Pointer to the read-write lock attributes |
return | —— |
0 | success |
-1 | Invalid parameter |
This function initializes the read-write lock attribute attr with the default value PTHREAD_PROCESS_PRIVATE.
parameter | describe |
attr | Pointer to the read-write lock attributes |
pshared | Pointer to the scope that holds the read-write lock |
return | —— |
0 | success |
-1 | Invalid parameter |
The memory pointed to by pshared holds the value PTHREAD_PROCESS_PRIVATE.
parameter | describe |
attr | Pointer to the barrier properties |
return | —— |
0 | success |
-1 | Invalid parameter |
This function initializes the barrier attribute attr with the default value PTHREAD_PROCESS_PRIVATE.
parameter | describe |
attr | Pointer to the barrier properties |
pshared | Pointer to data holding the barrier scope |
return | —— |
0 | success |
-1 | Invalid parameter |
The message queue property control block is as follows:
parameter | describe |
mqdes | Pointer to the message queue control block |
mqstat | Pointer to the data to be retrieved |
return | —— |
0 | success |
-1 | Invalid parameter |
Last updated