Linux编程基础——多线程

在Android UWB的硬件抽象层的实现中涉及比较多的linux多线程编程相关的内容,本文将相关知识进行简单梳理,以便能够进一步加深相关实现的理解。

1. 线程(pthread)

POSIX线程(pthread),是一种可移植的多线程标准。
Linux内核支持多种线程调度策略,如抢占式调度和时间片轮转调度,可以通过pthread_attr_t结构体来进行配置。
Linux中通过内核线程实现和用户空间线程库的结合,可以方便创建、管理和同步多线程应用程序。

以下为Linux线程的简单示例:

#include 

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine)(void *), void *arg);


#include 
#include 

void *thread_function(void *arg) {
    int *value = (int *)arg;
    printf("Thread value: %d\n", *value);
    // 其他操作...
    return NULL;
}

int main() {
    pthread_t thread;
    int value = 42;

    // 1. 主任务中创建新线程,线程立即执行
    if (pthread_create(&thread, NULL, thread_function, (void *)&value) != 0) {
        perror("pthread_create");
        return 1;
    }

    // 其他操作...

    // 2. 等待新线程结束
    if (pthread_join(thread, NULL) != 0) {
        perror("pthread_join");
        return 1;
    }

    return 0;
}

pthread_create用于创建一个新线程,线程将在函数返回后立即开始执行。
pthread_join函数等待创建的线程结束。
线程的执行顺序和调度由操作系统内核决定。
使用线程时需要小心处理共享资源的同步和互斥,以避免并发访问引发的问题。在线程函数中结合上信号量、互斥锁等常见的同步机制即可满足常规的多线程任务需求。

1.1 pthread_cond_***

pthread_cond_timedwait函数是POSIX线程库中用于条件变量的等待函数之一,它允许线程在指定的时间段内等待条件变量满足特定条件,线程继续执行。
pthread_cond_signal函数是POSIX线程库中用于条件变量的型号函数,用于唤醒正在等待该条件变量的一个线程。

这两个函数的定义如下:

#include 
// 互斥锁指针、abstime等待的绝对时间点
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex,
                           const struct timespec *restrict abstime);
int pthread_cond_signal(pthread_cond_t *cond);

pthread_cond_timedwait,若mutex未锁定,则返回错误;若已经锁定,函数将释放mutex,并将线程置于等待状态,直到条件变量被唤醒或设置的绝对时间abstime到达。当线程被唤醒时,将重新获取mutex并继续执行。

注意,在使用pthread_cond_timedwait函数时,需要保证互斥锁已被锁定,并在等待之前解锁。等待过程中,互斥锁会被自动重新锁定。

关于pthread_cond_signal,如果有线程正在等待该条件变量,将选择一个等待线程并将其唤醒;若没有线程等待该变量,则不会产生任何效果。

在Android UWB的实现中,关于phTmlUwb中,使用了线程等待函数来实现写等待操作的实现:

// 等待写操作完成
static void phTmlUwb_WaitWriteComplete(void) {
    int ret;
    struct timespec absTimeout;
    clock_gettime(CLOCK_MONOTONIC, &absTimeout);
    absTimeout.tv_sec += 1; /*1 second timeout*/
    gpphTmlUwb_Context->wait_busy_flag = true;

    //wait_busy_condition,wait_busy_lock互斥锁,等待写完成信号
    ret = pthread_cond_timedwait(&gpphTmlUwb_Context->wait_busy_condition,
                                 &gpphTmlUwb_Context->wait_busy_lock,
                                 &absTimeout);
}

// 写完成信号
static void phTmlUwb_SignalWriteComplete(void) {
  int ret;
  if (gpphTmlUwb_Context->wait_busy_flag == true) {
    gpphTmlUwb_Context->wait_busy_flag = false;
    ret = pthread_cond_signal(&gpphTmlUwb_Context->wait_busy_condition);
  }
}

// 关于信号量写完成调用
static void* phTmlUwb_TmlWriterThread(void* pParam)
{
    //...
    /* TML reader writer callback synchronization mutex lock --- START */
    pthread_mutex_lock(&gpphTmlUwb_Context->wait_busy_lock);
    gpphTmlUwb_Context->gWriterCbflag = true;
    phTmlUwb_SignalWriteComplete();
    /* TML reader writer callback synchronization mutex lock --- END */
    pthread_mutex_unlock(&gpphTmlUwb_Context->wait_busy_lock);
}

可以看到,在写信号之前,都需要将互斥锁加锁,进入临界区执行。

2. 信号量

信号量是Linux多线程编程中使用较多的一种同步工具,以便在多线程/多进程之间进行同步和互斥操作。

#include 
#include 

// pshared  是否为线程间共享,为0表示是线程间共享的,可在同一进程的不同线程之间使用;非0表示是进程间共享的,可以在不同进程之间使用。
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_post(sem_t *sem);
int sem_wait(sem_t *sem);

int main() {
    sem_t mySemaphore;

    // 初始化信号量,初始值为1
    if (sem_init(&mySemaphore, 0, 1) == -1) {
        perror("sem_init");
        return 1;
    }

    // 其他操作...

    // 增加信号量的值
    sem_post(&mySemaphore);

    // 销毁信号量
    sem_destroy(&mySemaphore);

    return 0;
}

sem_init只能用于未初始化的信号量,若是已经初始化的信号量,应先调用sem_destroy销毁之后再调用sem_init进行初始化。

sem_wait函数调用,若信号量的值大于0,则将被减1,继续执行;若信号量为0,则线程将被阻塞,直到其他线程或进程调用sem_post函数增加信号量的值。

进而通过sem_wait/sem_post实现线程或进程之间的同步和互斥操作。

在使用信号量时应保证正确的配对和顺序,以避免潜在的并发问题和死锁情况。

3. 互斥锁

pthread_mutex_t是POSIX线程库中用于互斥锁的数据类型。是线程同步的一种机制,用于保护共享资源的访问,以防止并发访问造成的数据竞争和不一致性。
通过以下函数进行初始化、锁定和解锁:

  • pthread_mutex_init,用于初始化互斥锁
  • pthread_mutex_destroy,用于销毁互斥锁
  • pthread_mutex_lock,用于锁定互斥锁,阻塞其他线程对互斥锁的访问,直到互斥锁解锁
  • pthread_mutex_unlock,用于解锁互斥锁,允许其他线程对互斥锁进行访问。
    //锁定与解锁之间构成临界区操作
    pthread_mutex_lock(&mutex);

    // 临界区操作...

    pthread_mutex_unlock(&mutex);

在UCI HAL实现中,我们可以看到许多与多线程实现相关的变量的定义,以UCI控制结构体为例:

/* UCI Control structure */
typedef struct phNxpUciHal_Control {
  pthread_t client_thread;      /* Integration thread handle */
	//...

  /* Waiting semaphore */
  phNxpUciHal_Sem_t ext_cb_data;
	
  phNxpUciHal_Sem_t dev_status_ntf_wait;
  phNxpUciHal_Sem_t uwb_binding_status_ntf_wait;

} phNxpUciHal_Control_t;

// 其中对信号量进行了一定的封装

/* Semaphore handling structure */
typedef struct phNxpUciHal_Sem {
  /* Semaphore used to wait for callback */
  sem_t sem;
  /* Used to store the status sent by the callback */
  tHAL_UWB_STATUS status;
  /* Used to provide a local context to the callback */
  void* pContext;
} phNxpUciHal_Sem_t;

你可能感兴趣的:(Linux,linux)