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_".

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.

  1. Enable pthread in menuconfig and set the maximum number of pthreads supported

  2. 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.

typedef rt_thread_t pthread_t;copymistakeCopy Success

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.

int pthread_create (pthread_t *tid,
                   const pthread_attr_t *attr,
                   void *(*start) (void *), void *arg);copymistakeCopy Success

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.

Create thread sample code

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.

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>

/* 线程控制块 */
static pthread_t tid1;
static pthread_t tid2;

/* 函数返回值检查 */
static void check_result(char* str,int result)
{
    if (0 == result)
    {
        printf("%s successfully!\n",str);
    }
    else
    {
        printf("%s failed! error code is %d\n",str,result);
    }
}

/* 线程入口函数 */
static void* thread_entry(void* parameter)
{
    int count = 0;
    int no = (int) parameter; /* 获得线程的入口参数 */

    while (1)
    {
        /* 打印输出线程计数值 */
        printf("thread%d count: %d\n", no, count ++);

        sleep(2);    /* 休眠 2 秒 */
    }
}

/* 用户应用入口 */
int rt_application_init()
{
    int result;

    /* 创建线程 1, 属性为默认值,入口函数是 thread_entry,入口函数参数是 1 */
    result = pthread_create(&tid1,NULL,thread_entry,(void*)1);
    check_result("thread1 created", result);

    /* 创建线程 2, 属性为默认值,入口函数是 thread_entry,入口函数参数是 2 */
    result = pthread_create(&tid2,NULL,thread_entry,(void*)2);
    check_result("thread2 created", result);

    return 0;
}copymistakeCopy Success

int pthread_detach (pthread_t thread);copymistakeCopy 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.

Detaching thread sample code

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.

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>

/* 线程控制块 */
static pthread_t tid1;
static pthread_t tid2;

/* 函数返回值检查 */
static void check_result(char* str,int result)
{
    if (0 == result)
    {
        printf("%s successfully!\n",str);
    }
    else
    {
        printf("%s failed! error code is %d\n",str,result);
    }
}

/* 线程 1 入口函数 */
static void* thread1_entry(void* parameter)
{
    int i;

    printf("i'm thread1 and i will detach myself!\n");
    pthread_detach(pthread_self());        /* 线程 1 脱离自己 */

    for (i = 0;i < 3;i++)    /* 循环打印 3 次信息 */
    {
        printf("thread1 run count: %d\n",i);
        sleep(2);    /* 休眠 2 秒 */
    }

    printf("thread1 exited!\n");
    return NULL;
}

/* 线程 2 入口函数 */
static void* thread2_entry(void* parameter)
{
    int i;

    for (i = 0;i < 3;i++)    /* 循环打印 3 次信息 */
    {
        printf("thread2 run count: %d\n",i);
        sleep(2);    /* 休眠 2 秒 */
    }

    printf("thread2 exited!\n");
    return NULL;
}
/* 用户应用入口 */
int rt_application_init()
{
    int result;

    /* 创建线程 1, 属性为默认值,分离状态为默认值 joinable,
     * 入口函数是 thread1_entry,入口函数参数为 NULL */
    result = pthread_create(&tid1,NULL,thread1_entry,NULL);
    check_result("thread1 created",result);

    /* 创建线程 2, 属性为默认值,分离状态为默认值 joinable,
     * 入口函数是 thread2_entry,入口函数参数为 NULL */
    result = pthread_create(&tid2,NULL,thread2_entry,NULL);
    check_result("thread2 created",result);

    pthread_detach(tid2);    /* 脱离线程 2 */

    return 0;
}copymistakeCopy Success

int pthread_join (pthread_t thread, void**value_ptr);copymistakeCopy Success

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.

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>

/* 线程控制块 */
static pthread_t tid1;
static pthread_t tid2;

/* 函数返回值检查 */
static void check_result(char* str,int result)
{
    if (0 == result)
    {
        printf("%s successfully!\n",str);
    }
    else
    {
        printf("%s failed! error code is %d\n",str,result);
    }
}

/* 线程 1 入口函数 */
static void* thread1_entry(void* parameter)
{
    int i;

    for (int i = 0;i < 3;i++)    /* 循环打印 3 次信息 */
    {
        printf("thread1 run count: %d\n",i);
        sleep(2);    /* 休眠 2 秒 */
    }

    printf("thread1 exited!\n");
    return NULL;
}

/* 线程 2 入口函数 */
static void* thread2_entry(void* parameter)
{
    int count = 0;
    void* thread1_return_value;

    /* 阻塞等待线程 1 运行结束 */
    pthread_join(tid1, NULL);

    /* 线程 2 打印信息开始输出 */
    while(1)
    {
        /* 打印线程计数值输出 */
        printf("thread2 run count: %d\n",count ++);
        sleep(2);    /* 休眠 2 秒 */
    }

    return NULL;
}

/* 用户应用入口 */
int rt_application_init()
{
    int result;
    /* 创建线程 1, 属性为默认值,分离状态为默认值 joinable,
     * 入口函数是 thread1_entry,入口函数参数为 NULL */
    result = pthread_create(&tid1,NULL,thread1_entry,NULL);
    check_result("thread1 created",result);

    /* 创建线程 2, 属性为默认值,分离状态为默认值 joinable,
     * 入口函数是 thread2_entry,入口函数参数为 NULL */
    result = pthread_create(&tid2,NULL,thread2_entry,NULL);
    check_result("thread2 created",result);

    return 0;
}copymistakeCopy Success

void pthread_exit(void *value_ptr);copymistakeCopy Success

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.

Exit thread sample code

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.

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>

/* 线程控制块 */
static pthread_t tid1;
static pthread_t tid2;

/* 函数返回值核对函数 */
static void check_result(char* str,int result)
{
    if (0 == result)
    {
        printf("%s successfully!\n",str);
    }
    else
    {
        printf("%s failed! error code is %d\n",str,result);
    }
}

/* 线程 1 入口函数 */
static void* thread1_entry(void* parameter)
{
    int count = 0;
    while(1)
    {
        /* 打印线程计数值输出 */
        printf("thread1 run count: %d\n",count ++);
        sleep(2);    /* 休眠 2 秒 */
        printf("thread1 will exit!\n");

        pthread_exit(0);    /* 线程 1 主动退出 */
    }
}

/* 线程 2 入口函数 */
static void* thread2_entry(void* parameter)
{
    int count = 0;

    /* 阻塞等待线程 1 运行结束 */
    pthread_join(tid1,NULL);
    /* 线程 2 开始输出打印信息 */
    while(1)
    {
        /* 打印线程计数值输出 */
        printf("thread2 run count: %d\n",count ++);
        sleep(2);    /* 休眠 2 秒 */
    }
}

/* 用户应用入口 */
int rt_application_init()
{
    int result;

    /* 创建线程 1, 属性为默认值,分离状态为默认值 joinable,
     * 入口函数是 thread1_entry,入口函数参数为 NULL */
    result = pthread_create(&tid1,NULL,thread1_entry,NULL);
    check_result("thread1 created",result);

    /* 创建线程 2, 属性为默认值,分离状态为默认值 joinable,
     * 入口函数是 thread2_entry,入口函数参数为 NULL */
    result = pthread_create(&tid2,NULL,thread2_entry,NULL);
    check_result("thread2 created",result);

    return 0;
}copymistakeCopy Success

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:

struct pthread_mutex
{
    pthread_mutexattr_t attr;    /* 互斥锁属性 */
    struct rt_mutex lock;    /* RT-Thread 互斥锁控制块 */
};
typedef struct pthread_mutex pthread_mutex_t;

rt_mutex 是 RT-Thread 内核里定义的一个数据结构,定义在 rtdef.h 头文件里,数据结构如下:

struct rt_mutex
{
    struct rt_ipc_object parent;                /* 继承自 ipc_object 类 */
    rt_uint16_t          value;                  /* 互斥锁的值 */
    rt_uint8_t           original_priority;     /* 持有线程的原始优先级 */
    rt_uint8_t           hold;                    /* 互斥锁持有计数  */
    struct rt_thread    *owner;                 /* 当前拥有互斥锁的线程 */
};
typedef struct rt_mutex* rt_mutex_t;        /* rt_mutext_t 为指向互斥锁结构体的指针 */copymistakeCopy Success

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);copymistakeCopy Success

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.

int pthread_mutex_destroy(pthread_mutex_t *mutex);copymistakeCopy Success

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.

int pthread_mutex_lock(pthread_mutex_t *mutex);copymistakeCopy Success

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.

int pthread_mutex_trylock(pthread_mutex_t *mutex);copymistakeCopy Success

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.

int pthread_mutex_unlock(pthread_mutex_t *mutex);copymistakeCopy Success

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.

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>

/* 线程控制块 */
static pthread_t tid1;
static pthread_t tid2;
/* 互斥锁控制块 */
static pthread_mutex_t mutex;
/* 线程共享的打印函数 */
static void printer(char* str)
{
    while(*str != 0)
    {
        putchar(*str);    /* 输出一个字符 */
        str++;
        sleep(1);    /* 休眠 1 秒 */
    }
    printf("\n");
}
/* 函数返回值检查 */
static void check_result(char* str,int result)
{
    if (0 == result)
    {
        printf("%s successfully!\n",str);
    }
    else
    {
        printf("%s failed! error code is %d\n",str,result);
    }
}
/* 线程入口 */
static void* thread1_entry(void* parameter)
{
    char* str = "thread1 hello RT-Thread";
    while (1)
    {
        pthread_mutex_lock(&mutex);      /* 互斥锁上锁 */

        printer(str);  /* 访问共享打印函数 */

        pthread_mutex_unlock(&mutex);  /* 访问完成后解锁 */

        sleep(2);    /* 休眠 2 秒 */
    }
}
static void* thread2_entry(void* parameter)
{
    char* str = "thread2 hi world";
    while (1)
    {
        pthread_mutex_lock(&mutex);  /* 互斥锁上锁 */

        printer(str);  /* 访问共享打印函数 */

        pthread_mutex_unlock(&mutex);  /* 访问完成后解锁 */

        sleep(2);    /* 休眠 2 秒 */
    }
}
/* 用户应用入口 */
int rt_application_init()
{
    int result;
    /* 初始化一个互斥锁 */
    pthread_mutex_init(&mutex,NULL);

    /* 创建线程 1, 线程入口是 thread1_entry, 属性参数为 NULL 选择默认值,入口参数是 NULL*/
    result = pthread_create(&tid1,NULL,thread1_entry,NULL);
    check_result("thread1 created",result);

    /* 创建线程 2, 线程入口是 thread2_entry, 属性参数为 NULL 选择默认值,入口参数是 NULL*/
    result = pthread_create(&tid2,NULL,thread2_entry,NULL);
    check_result("thread2 created",result);

    return 0;
}copymistakeCopy Success

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.

struct pthread_cond
{
    pthread_condattr_t attr;         /* 条件变量属性 */
    struct rt_semaphore sem;        /* RT-Thread 信号量控制块 */
};
typedef struct pthread_cond pthread_cond_t;

rt_semaphore 是 RT-Thread 内核里定义的一个数据结构,是信号量控制块,
定义在 rtdef.h 头文件里

struct rt_semaphore
{
   struct rt_ipc_object parent;/* 继承自 ipc_object 类 */
   rt_uint16_t value;   /* 信号量的值  */
};copymistakeCopy Success

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);copymistakeCopy Success

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.

int pthread_cond_destroy(pthread_cond_t *cond);copymistakeCopy Success

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.

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);copymistakeCopy Success

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.

int pthread_cond_timedwait(pthread_cond_t *cond,
                          pthread_mutex_t *mutex,
                          const struct timespec *abstime);copymistakeCopy Success

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.

int pthread_cond_signal(pthread_cond_t *cond);copymistakeCopy 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.

int pthread_cond_broadcast(pthread_cond_t *cond);copymistakeCopy Success

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.

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

/* 静态方式初始化一个互斥锁和一个条件变量 */
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

/* 指向线程控制块的指针 */
static pthread_t tid1;
static pthread_t tid2;

/* 函数返回值检查 */
static void check_result(char* str,int result)
{
    if (0 == result)
    {
        printf("%s successfully!\n",str);
    }
    else
    {
        printf("%s failed! error code is %d\n",str,result);
    }
}

/* 生产者生产的结构体数据,存放在链表里 */
struct node
{
    int n_number;
    struct node* n_next;
};
struct node* head = NULL; /* 链表头, 是共享资源 */

/* 消费者线程入口函数 */
static void* consumer(void* parameter)
{
    struct node* p_node = NULL;

    pthread_mutex_lock(&mutex);    /* 对互斥锁上锁 */

    while (1)
    {
        while (head == NULL)    /* 判断链表里是否有元素 */
        {
            pthread_cond_wait(&cond,&mutex); /* 尝试获取条件变量 */
        }
        /*
        pthread_cond_wait() 会先对 mutex 解锁,
        然后阻塞在等待队列,直到获取条件变量被唤醒,
        被唤醒后,该线程会再次对 mutex 上锁,成功进入临界区。
        */

        p_node = head;    /* 拿到资源 */
        head = head->n_next;    /* 头指针指向下一个资源 */
        /* 打印输出 */
        printf("consume %d\n",p_node->n_number);

        free(p_node);    /* 拿到资源后释放节点占用的内存 */
    }
    pthread_mutex_unlock(&mutex);    /* 释放互斥锁 */
    return 0;
}
/* 生产者线程入口函数 */
static void* product(void* patameter)
{
    int count = 0;
    struct node *p_node;

    while(1)
    {
        /* 动态分配一块结构体内存 */
        p_node = (struct node*)malloc(sizeof(struct node));
        if (p_node != NULL)
        {
            p_node->n_number = count++;
            pthread_mutex_lock(&mutex);    /* 需要操作 head 这个临界资源,先加锁 */

            p_node->n_next = head;
            head = p_node;    /* 往链表头插入数据 */

            pthread_mutex_unlock(&mutex);    /* 解锁 */
            printf("produce %d\n",p_node->n_number);

            pthread_cond_signal(&cond);    /* 发信号唤醒一个线程 */

            sleep(2);    /* 休眠 2 秒 */
        }
        else
        {
            printf("product malloc node failed!\n");
            break;
        }
    }
}

int rt_application_init()
{
    int result;

    /* 创建生产者线程, 属性为默认值,入口函数是 product,入口函数参数为 NULL*/
    result = pthread_create(&tid1,NULL,product,NULL);
    check_result("product thread created",result);

    /* 创建消费者线程, 属性为默认值,入口函数是 consumer,入口函数参数是 NULL */
    result = pthread_create(&tid2,NULL,consumer,NULL);
    check_result("consumer thread created",result);

    return 0;
}copymistakeCopy Success

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.

struct pthread_rwlock
{
    pthread_rwlockattr_t attr;    /* 读写锁属性 */
    pthread_mutex_t rw_mutex;    /* 互斥锁 */
    pthread_cond_t rw_condreaders;    /* 条件变量,供读者线程使用 */
    pthread_cond_t rw_condwriters;    /* 条件变量,供写者线程使用 */
    int rw_nwaitreaders;    /* 读者线程等待计数 */
    int rw_nwaitwriters;    /* 写者线程等待计数 */
    /* 读写锁值,值为 0:未上锁, 值为 - 1:被写者线程锁定, 大于 0 值:被读者线程锁定数量 */
    int rw_refcount;
};
typedef struct pthread_rwlock pthread_rwlock_t;        /* 类型重定义 */copymistakeCopy Success

int pthread_rwlock_init (pthread_rwlock_t *rwlock,
                            const pthread_rwlockattr_t *attr);copymistakeCopy Success

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.

int pthread_rwlock_destroy (pthread_rwlock_t *rwlock);copymistakeCopy Success

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

int pthread_rwlock_rdlock (pthread_rwlock_t *rwlock);copymistakeCopy Success

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

int pthread_rwlock_tryrdlock (pthread_rwlock_t *rwlock);copymistakeCopy Success

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

int pthread_rwlock_timedrdlock (pthread_rwlock_t *rwlock,
                                const struct timespec *abstime);copymistakeCopy Success

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

int pthread_rwlock_wrlock (pthread_rwlock_t *rwlock);copymistakeCopy Success

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

int pthread_rwlock_trywrlock (pthread_rwlock_t *rwlock);copymistakeCopy Success

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

int pthread_rwlock_timedwrlock (pthread_rwlock_t *rwlock,
                                const struct timespec *abstime);copymistakeCopy Success

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.

int pthread_rwlock_unlock (pthread_rwlock_t *rwlock);copymistakeCopy Success

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.

#include <pthread.h>
#include <sched.h>
#include <stdio.h>

/* 线程控制块 */
static pthread_t reader1;
static pthread_t reader2;
static pthread_t writer1;
/* 共享数据 book */
static int book = 0;
/* 读写锁 */
static pthread_rwlock_t rwlock;
/* 函数结果检查 */
static void check_result(char* str,int result)
{
    if (0 == result)
    {
        printf("%s successfully!\n",str);
    }
    else
    {
        printf("%s failed! error code is %d\n",str,result);
    }
}
/* 线程入口 */
static void* reader1_entry(void* parameter)
{
    while (1)
    {

        pthread_rwlock_rdlock(&rwlock);  /* 尝试读锁定该读写锁 */

        printf("reader1 read book value is %d\n",book);
        sleep(2);  /* 线程休眠 2 秒,切换到其他线程运行 */

        pthread_rwlock_unlock(&rwlock);  /* 线程运行后对读写锁解锁 */
    }
}
static void* reader2_entry(void* parameter)
{
    while (1)
    {
        pthread_rwlock_rdlock(&rwlock);  /* 尝试读锁定该读写锁 */

        printf("reader2 read book value is %d\n",book);
        sleep(2);  /* 线程休眠 2 秒,切换到其他线程运行 */

        pthread_rwlock_unlock(&rwlock);  /* 线程运行后对读写锁解锁 */
    }
}
static void* writer1_entry(void* parameter)
{
    while (1)
    {
        pthread_rwlock_wrlock(&rwlock);  /* 尝试写锁定该读写锁 */

        book++;
        printf("writer1 write book value is %d\n",book);

        pthread_rwlock_unlock(&rwlock);  /* 对读写锁解锁 */

        sleep(2);  /* 线程休眠 2 秒,切换到其他线程运行 */
    }
}
/* 用户应用入口 */
int rt_application_init()
{
    int result;
    /* 默认属性初始化读写锁 */
    pthread_rwlock_init(&rwlock,NULL);

    /* 创建 reader1 线程, 线程入口是 reader1_entry, 线程属性为默认值,入口参数为 NULL*/
    result = pthread_create(&reader1,NULL,reader1_entry,NULL);
    check_result("reader1 created",result);

    /* 创建 reader2 线程, 线程入口是 reader2_entry, 线程属性为默认值,入口参数为 NULL*/
    result = pthread_create(&reader2,NULL,reader2_entry,NULL);
    check_result("reader2 created",result);

    /* 创建 writer1 线程, 线程入口是 writer1_entry, 线程属性为,入口参数为 NULL*/
    result = pthread_create(&writer1,NULL,writer1_entry,NULL);
    check_result("writer1 created",result);

    return 0;
}copymistakeCopy Success

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.

struct pthread_barrier
{
    int count;    /* 指定的等待线程个数 */
    pthread_cond_t cond;        /* 条件变量 */
    pthread_mutex_t mutex;    /* 互斥锁 */
};
typedef struct pthread_barrier pthread_barrier_t;copymistakeCopy Success

int pthread_barrier_init(pthread_barrier_t *barrier,
                        const pthread_barrierattr_t *attr,
                        unsigned count);copymistakeCopy Success

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.

int pthread_barrier_destroy(pthread_barrier_t *barrier);copymistakeCopy Success

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().

int pthread_barrier_wait(pthread_barrier_t *barrier);copymistakeCopy Success

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.

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>

/* 线程控制块 */
static pthread_t tid1;
static pthread_t tid2;
static pthread_t tid3;
/* 屏障控制块 */
static pthread_barrier_t barrier;
/* 函数返回值检查函数 */
static void check_result(char* str,int result)
{
    if (0 == result)
    {
            printf("%s successfully!\n",str);
    }
    else
    {
            printf("%s failed! error code is %d\n",str,result);
    }
}
/* 线程 1 入口函数 */
static void* thread1_entry(void* parameter)
{
    int count = 0;

    printf("thread1 have arrived the barrier!\n");
    pthread_barrier_wait(&barrier);    /* 到达屏障,并等待其他线程到达 */

    while (1)
    {
        /* 打印线程计数值输出 */
        printf("thread1 count: %d\n",count ++);

        /* 休眠 2 秒 */
        sleep(2);
    }
}
/* 线程 2 入口函数 */
static void* thread2_entry(void* parameter)
{
    int count = 0;

    printf("thread2 have arrived the barrier!\n");
    pthread_barrier_wait(&barrier);

    while (1)
    {
        /* 打印线程计数值输出 */
        printf("thread2 count: %d\n",count ++);

        /* 休眠 2 秒 */
        sleep(2);
    }
}
/* 线程 3 入口函数 */
static void* thread3_entry(void* parameter)
{
    int count = 0;

    printf("thread3 have arrived the barrier!\n");
    pthread_barrier_wait(&barrier);

    while (1)
    {
        /* 打印线程计数值输出 */
        printf("thread3 count: %d\n",count ++);

        /* 休眠 2 秒 */
        sleep(2);
    }
}
/* 用户应用入口 */
int rt_application_init()
{
    int result;
    pthread_barrier_init(&barrier,NULL,3);

    /* 创建线程 1, 线程入口是 thread1_entry, 属性参数设为 NULL 选择默认值,入口参数为 NULL*/
    result = pthread_create(&tid1,NULL,thread1_entry,NULL);
    check_result("thread1 created",result);

    /* 创建线程 2, 线程入口是 thread2_entry, 属性参数设为 NULL 选择默认值,入口参数为 NULL*/
    result = pthread_create(&tid2,NULL,thread2_entry,NULL);
    check_result("thread2 created",result);

    /* 创建线程 3, 线程入口是 thread3_entry, 属性参数设为 NULL 选择默认值,入口参数为 NULL*/
    result = pthread_create(&tid3,NULL,thread3_entry,NULL);
    check_result("thread3 created",result);

}copymistakeCopy Success

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:

/* pthread_attr_t 类型重定义 */
typedef struct pthread_attr pthread_attr_t;
/* 线程属性结构体 */
struct pthread_attr
{
    void*      stack_base;        /* 线程栈的地址 */
    rt_uint32_t stack_size;     /* 线程栈大小 */
    rt_uint8_t priority;        /* 线程优先级 */
    rt_uint8_t detachstate;     /* 线程的分离状态 */
    rt_uint8_t policy;          /* 线程调度策略 */
    rt_uint8_t inheritsched;    /* 线程的继承性 */
};copymistakeCopy Success

Thread attribute initialization and deinitialization

The thread attribute initialization and deinitialization functions are as follows:

int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);copymistakeCopy 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.

Thread detached state

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.

int pthread_attr_setdetachstate(pthread_attr_t *attr, int state);
int pthread_attr_getdetachstate(pthread_attr_t const *attr, int *state);copymistakeCopy 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.

Thread Scheduling Policy

The function to set and get the thread scheduling policy is as follows:

int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
int pthread_attr_getschedpolicy(pthread_attr_t const *attr, int *policy);copymistakeCopy Success

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.

Thread scheduling parameters

The function to set thread priority/get thread priority is as follows:

int pthread_attr_setschedparam(pthread_attr_t *attr,
                               struct sched_param const *param);
int pthread_attr_getschedparam(pthread_attr_t const *attr,
                               struct sched_param *param);copymistakeCopy 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:

struct sched_param
{
    int sched_priority;    /* 线程优先级 */
};copymistakeCopy Success

The sched_priority member of the sched_param structure controls the priority value of the thread.

The thread stack size

The functions to set/get the thread's stack size are as follows:

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stack_size);
int pthread_attr_getstacksize(pthread_attr_t const *attr, size_t *stack_size);copymistakeCopy 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).

Thread stack size and address

The functions for setting/getting the thread's stack address and stack size are as follows:

int pthread_attr_setstack(pthread_attr_t *attr,
                          void *stack_base,
                          size_t stack_size);
int pthread_attr_getstack(pthread_attr_t const *attr,
                          void**stack_base,
                          size_t *stack_size);copymistakeCopy Success

Thread attribute related functions

The functions for setting/getting the scope of a thread are as follows:

int pthread_attr_setscope(pthread_attr_t *attr, int scope);
int pthread_attr_getscope(pthread_attr_t const *attr);copymistakeCopy Success

Thread attributes sample code

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.

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <sched.h>

/* 线程控制块 */
static pthread_t tid1;
static pthread_t tid2;

/* 函数返回值检查 */
static void check_result(char* str,int result)
{
    if (0 == result)
    {
        printf("%s successfully!\n",str);
    }
    else
    {
        printf("%s failed! error code is %d\n",str,result);
    }
}
/* 线程入口函数 */
static void* thread_entry(void* parameter)
{
    int count = 0;
    int no = (int) parameter; /* 获得线程的入口参数 */

    while (1)
    {
        /* 打印输出线程计数值 */
        printf("thread%d count: %d\n", no, count ++);

        sleep(2);    /* 休眠 2 秒 */
    }
}

/* 用户应用入口 */
int rt_application_init()
{
    int result;
    pthread_attr_t attr;      /* 线程属性 */
    struct sched_param prio;  /* 线程优先级 */

    prio.sched_priority = 8;  /* 优先级设置为 8 */
    pthread_attr_init(&attr);  /* 先使用默认值初始化属性 */
    pthread_attr_setschedparam(&attr,&prio);  /* 修改属性对应的优先级 */

    /* 创建线程 1, 属性为 attr,入口函数是 thread_entry,入口函数参数是 1 */
    result = pthread_create(&tid1,&attr,thread_entry,(void*)1);
    check_result("thread1 created",result);

    /* 创建线程 2, 属性为默认值,入口函数是 thread_entry,入口函数参数是 2 */
    result = pthread_create(&tid2,NULL,thread_entry,(void*)2);
    check_result("thread2 created",result);

    return 0;
}copymistakeCopy Success

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.

Send Cancellation Request

You can send a cancellation request using the following function:

int pthread_cancel(pthread_t thread);copymistakeCopy 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.

Set Cancel Status

You can use the following function to set a cancellation request:

int pthread_setcancelstate(int state, int *oldstate);copymistakeCopy Success

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.

Set the cancellation type

The cancellation type can be set using the following function, which is called by the thread itself:

int pthread_setcanceltype(int type, int *oldtype);copymistakeCopy Success

Setting Cancellation Points

You can use the following function to set the cancellation point:

void pthread_testcancel(void);copymistakeCopy Success

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.

Cancellation Point

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.

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>

/* 线程控制块 */
static pthread_t tid1;
static pthread_t tid2;

/* 函数返回值检查 */
static void check_result(char* str,int result)
{
    if (0 == result)
    {
        printf("%s successfully!\n",str);
    }
    else
    {
        printf("%s failed! error code is %d\n",str,result);
    }
}
/* 线程 1 入口函数 */
static void* thread1_entry(void* parameter)
{
    int count = 0;
    /* 设置线程 1 的取消状态使能,取消类型为线程收到取消点后马上结束 */
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);

    while(1)
    {
        /* 打印线程计数值输出 */
        printf("thread1 run count: %d\n",count ++);
        sleep(2);    /* 休眠 2 秒 */
    }
}
/* 线程 2 入口函数 */
static void* thread2_entry(void* parameter)
{
    int count = 0;
    sleep(8);
    /* 向线程 1 发送取消请求 */
    pthread_cancel(tid1);
    /* 阻塞等待线程 1 运行结束 */
    pthread_join(tid1,NULL);
    printf("thread1 exited!\n");
    /* 线程 2 打印信息开始输出 */
    while(1)
    {
        /* 打印线程计数值输出 */
        printf("thread2 run count: %d\n",count ++);
        sleep(2);    /* 休眠 2 秒 */
    }
}
/* 用户应用入口 */
int rt_application_init()
{
    int result;
    /* 创建线程 1, 属性为默认值,分离状态为默认值 joinable,
     入口函数是 thread1_entry,入口函数参数为 NULL */
    result = pthread_create(&tid1,NULL,thread1_entry,NULL);
    check_result("thread1 created",result);

    /* 创建线程 2, 属性为默认值,分离状态为默认值 joinable,
     入口函数是 thread2_entry,入口函数参数为 NULL */
    result = pthread_create(&tid2,NULL,thread2_entry,NULL);
    check_result("thread2 created",result);

    return 0;
}copymistakeCopy Success

You can use the following function to initialize it once:

int pthread_once(pthread_once_t * once_control, void (*init_routine) (void));copymistakeCopy 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:

void pthread_cleanup_pop(int execute);
void pthread_cleanup_push(void (*routine)(void*), void *arg);copymistakeCopy Success

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

int pthread_equal (pthread_t t1, pthread_t t2);copymistakeCopy Success

Get the thread handle

pthread_t pthread_self (void);copymistakeCopy Success

pthread_self() returns the handle of the calling thread.

Get the maximum and minimum priorities

int sched_get_priority_min(int policy);
int sched_get_priority_max(int policy);copymistakeCopy Success

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

int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);copymistakeCopy Success

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.

Mutex Scope

int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int  pshared);
int pthread_mutexattr_getpshared(pthread_mutexattr_t *attr, int *pshared);copymistakeCopy Success

Mutex Type

int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);
int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type);copymistakeCopy Success

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:

int pthread_condattr_init(pthread_condattr_t *attr);copymistakeCopy Success

Get the scope of a condition variable

int pthread_mutexattr_getpshared(pthread_mutexattr_t *attr, int *pshared);copymistakeCopy Success

Initializing Properties

int pthread_rwlockattr_init (pthread_rwlockattr_t *attr);copymistakeCopy Success

This function initializes the read-write lock attribute attr with the default value PTHREAD_PROCESS_PRIVATE.

Get scope

int pthread_rwlockattr_getpshared (const pthread_rwlockattr_t *attr, int *pshared);copymistakeCopy Success

The memory pointed to by pshared holds the value PTHREAD_PROCESS_PRIVATE.

Initializing Properties

int pthread_barrierattr_init(pthread_barrierattr_t *attr);copymistakeCopy Success

This function initializes the barrier attribute attr with the default value PTHREAD_PROCESS_PRIVATE.

Get scope

int pthread_barrierattr_getpshared(const pthread_barrierattr_t *attr, int *pshared);copymistakeCopy Success

The message queue property control block is as follows:

struct mq_attr
{
    long mq_flags;      /* 消息队列的标志,用来表示是否阻塞 */
    long mq_maxmsg;     /* 消息队列最大消息数 */
    long mq_msgsize;    /* 消息队列每个消息的最大字节数 */
    long mq_curmsgs;    /* 消息队列当前消息数 */
};copymistakeCopy Success

Get properties

int mq_getattr(mqd_t mqdes, struct mq_attr *mqstat);copymistakeCopy Success

Last updated

Assoc. Prof. Wiroon Sriborrirux, Founder of Advance Innovation Center (AIC) and Bangsaen Design House (BDH), Electrical Engineering Department, Faculty of Engineering, Burapha University