线程

pthread_create函数 线程创建

pthread_create 是 POSIX 线程(通常称为 Pthreads)库中的核心函数,用于创建一个新的线程。该线程在调用进程的地址空间内执行,并与进程内的其他线程共享除局部变量(栈空间)外的几乎所有资源,如全局变量、文件描述符、堆内存等。

函数原型

#include <pthread.h>

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

函数解析

这个函数有四个参数,我们逐一分解:

1. pthread_t *restrict thread

  • 用途:这是一个出参(输出参数)。函数成功返回后,会在这个参数指向的内存中填入新创建线程的线程标识符(ID)
  • 类型pthread_t 是一个不透明的数据类型,用于唯一地标识一个线程。你不能直接把它当作整数来操作(尽管在某些实现中它可能是整数),而应该使用 Pthreads 库提供的函数(如 pthread_equal)来操作它。
  • restrict 关键字:这是一个 C 语言的关键字,告诉编译器这个指针是访问这块内存的唯一途径,便于编译器进行优化。对于使用者来说,可以忽略它。

2. const pthread_attr_t *restrict attr

  • 用途:用于设置新线程的属性。如果传递 NULL,则线程使用默认属性创建(非分离、默认栈大小、与父线程相同的调度策略等)。
  • 类型pthread_attr_t 是一个结构体,用于设置线程的各种属性,例如:
    • 分离状态(detached state):PTHREAD_CREATE_JOINABLE(默认)或 PTHREAD_CREATE_DETACHED
    • 调度策略和优先级(scheduling policy and priority)。
    • 栈大小(stack size)。
    • 等等
  • 通常做法:对于大多数简单应用,直接传递 NULL 使用默认属性就足够了。如果需要精细控制线程的行为(例如创建一个分离线程),则需要先使用 pthread_attr_init 等相关函数初始化并配置一个属性对象,然后传递进来。

3. void *(*start_routine)(void *)

  • 用途:这是新线程开始执行的函数指针。线程创建成功后,就会从这个函数开始运行。
  • 签名解析:这是一个函数指针,它指向的函数必须符合以下格式:
    • 返回类型是 void *
    • 参数类型是 void *
    • 例如:void* my_thread_function(void* arg)

4. void *restrict arg

  • 用途:这是传递给 start_routine 函数的参数
  • 类型void *。这是一个通用指针,意味着你可以传递任何类型的指针(如 int*char*, 或者一个指向复杂结构体的指针)。在线程函数内部,你需要将它转换回原来的类型。
  • 注意不要传递指向栈变量的指针! 如果主线程(调用者)的函数返回,其栈帧会被销毁,那么这个指针就会变成野指针。确保传递的数据在全局数据区(如全局变量、静态变量)或堆上(用 malloc 分配),或者确保主线程会等待新线程结束。

返回值

  • 成功:返回 0
  • 失败:返回一个错误码(非零值),而不是设置 errno。常见的错误码有:
    • EAGAIN:系统资源不足,无法创建另一个线程,或线程数量超过了系统的限制。
    • EINVALattr 参数无效。
    • EPERM:没有权限设置指定的调度策略或参数。

示例:

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

// 线程函数必须符合 void* (*)(void*) 的格式
void* thread_function(void* arg) {
    int thread_num = *((int*)arg); // 将 void* 参数转换回 int*
    printf("Hello from thread %d!\n", thread_num);
    pthread_exit(NULL); // 线程退出,可以返回一个值,这里返回 NULL
}

int main() {
    pthread_t thread_id;
    int arg_value = 42; // 要传递给线程的参数

    // 创建线程
    int ret = pthread_create(&thread_id,   // 线程 ID
                            NULL,          // 默认属性
                            thread_function, // 线程函数
                            (void*)&arg_value); // 传递给线程函数的参数

    if (ret != 0) {
        perror("pthread_create failed");
        exit(EXIT_FAILURE);
    }

    printf("Main thread: created thread with ID ...\n");

    // 等待指定的线程结束。
    // 这里是为了防止主线程先退出导致整个进程退出,从而杀死新线程。
    pthread_join(thread_id, NULL);

    return 0;
}

编译和链接
由于 pthread_create 在 POSIX 线程库中,编译时需要加上 -pthread(或在某些系统上是 -lpthread)标志来链接库。

gcc -o my_program my_program.c -pthread

pthread_exit函数 线程退出

pthread_exit 用于显式地终止当前调用它的线程。它是一个线程的“优雅退出”方式,允许线程在结束前执行清理操作(例如,解锁互斥锁),并可以选择一个返回值供其他线程(通过pthread_join)获取。

线程也可以通过从其启动函数中 return 来隐式终止,但使用 pthread_exit 可以提供更明确的控制,尤其是在嵌套函数调用中退出时。

函数原型

#include <pthread.h>

void pthread_exit(void *retval);

参数解析

void *retval

  • 用途:这是线程的退出状态(exit status)。这个值可以被其他通过 pthread_join 等待该线程结束的线程获取
  • 类型void *。这是一个通用指针,意味着你可以返回任何类型的指针(如 int*char*, 或者一个指向复杂结构体的指针),也可以返回 NULL
  • 重要警告绝对不能返回一个指向线程局部变量(栈上变量)的指针! 因为当线程退出时,它的栈空间会被销毁,这个指针就会变成野指针。返回的数据必须在:
    • 全局数据区(如全局变量、静态变量)。
    • 堆上(用 malloccallocnew 等分配)。
    • 或者是一个常数值(通常需要强制转换为 void *)。

返回值

pthread_exit 函数不返回。一旦调用,当前线程就停止执行。


pthread_join函数,线程等待

pthread_join 是 POSIX 线程(Pthreads)库中用于线程同步和资源回收的核心函数。它的主要作用是:阻塞当前线程,直到指定的目标线程终止,并获取目标线程的退出状态(返回值)

你可以把它看作进程编程中的 wait() 或 waitpid() 函数在多线程环境下的对应物。它是管理线程生命周期不可或缺的一部分。

函数原型

#include <pthread.h>

int pthread_join(pthread_t thread, void **retval);

参数解析

1. pthread_t thread

  • 用途:指定要等待的目标线程的标识符(ID)
  • 类型pthread_t。这个 ID 是在调用 pthread_create 创建线程时获得的。

2. void **retval

  • 用途:这是一个出参(输出参数),一个指向指针的指针。函数成功返回后,这里将存储目标线程的退出状态。
    • 如果目标线程正常返回或调用了 pthread_exit(value),则 *retval 会包含 value
    • 如果目标线程被取消(pthread_cancel),则 *retval 会被设置为特殊值 PTHREAD_CANCELED
    • 如果你不关心线程的退出状态,可以在此传入 NULL
  • 工作原理
    • 你传入一个 void* 类型变量的地址(即 void**)。
    • pthread_join 函数内部会将目标线程的返回值(一个 void*)写入你提供的这个地址。
    • 之后,你需要将这个 void* 转换回它原本的类型来处理它。

返回值

  • 成功:返回 0
  • 失败:返回一个错误码(非零值),而不是设置 errno。常见的错误码有:
    • EDEADLK:检测到死锁。例如,线程A等待线程B,而线程B也在等待线程A。
    • EINVAL:目标线程不是一个可连接线程(joinable thread)(例如,它可能是一个分离线程)。
    • ESRCH:找不到对应的线程。提供的 pthread_t ID 是无效的。

pthread_self函数 ,线程ID获取

pthread_self 是一个非常简单但非常重要的函数,它的作用是获取调用此函数的线程自身的线程标识符(ID)

你可以把它类比为进程编程中的 getpid() 函数。就像每个进程有一个唯一的进程ID(PID)一样,每个线程也有一个在其所属进程内唯一的线程ID。

函数原型

#include <pthread.h>

pthread_t pthread_self(void);

参数与返回值

  • 参数:无。
  • 返回值:返回调用该函数的线程自身的 pthread_t 类型的ID。

代码

#include <stdio.h>      // 标准输入输出库,提供printf等函数
#include <pthread.h>    // POSIX线程库,提供多线程编程接口

// 线程函数 - 新线程将从此函数开始执行
void *func1(void *arg)  // 线程函数必须符合此签名:返回void*,接受一个void*参数
{
   static int ret = 10; // 静态变量,生命周期贯穿整个程序,确保线程退出后返回值仍然有效

   // 打印当前线程ID和传入的参数
   printf("t1:%lu thread is created\n", (unsigned long)pthread_self()); // pthread_self()获取当前线程ID
   printf("t1:param is %d\n", *((int *)arg)); // 将void*参数转换回int*并解引用获取值

   pthread_exit((void *)&ret); // 退出线程并返回静态变量ret的地址
}

int main()
{
    int ret;            // 用于存储pthread_create函数的返回值
    int param = 100;    // 要传递给线程的参数
    pthread_t t1;       // 线程标识符,用于存储新创建线程的ID

    int *pret = NULL;   // 用于接收线程退出时的返回值

    // 创建新线程
    // 参数1: &t1 - 线程标识符的地址,创建成功后这里会存储新线程的ID
    // 参数2: NULL - 线程属性,NULL表示使用默认属性
    // 参数3: func1 - 线程函数的指针,新线程将从该函数开始执行
    // 参数4: (void *)&param - 传递给线程函数的参数,需要转换为void*类型
    ret = pthread_create(&t1, NULL, func1, (void *)&param);

    // 检查线程是否创建成功
    if(ret == 0)        // pthread_create返回0表示成功
    {
        printf("main:create t1 success\n");
    }
    else
    {
        // 实际编程中应该处理错误情况
        printf("main:create t1 failed with error %d\n", ret);
        return 1;
    }

    // 打印主线程的ID
    printf("main:%lu\n", (unsigned long)pthread_self());

    // 等待线程t1结束,并获取其返回值
    // 参数1: t1 - 要等待的线程标识符
    // 参数2: (void **)&pret - 指向指针的指针,用于接收线程的返回值
    pthread_join(t1, (void **)&pret);

    // 打印线程t1的返回值
    printf("main:t1 quit:%d\n", *pret); // 解引用pret获取线程返回的值

    return 0;
}

线程共享内存空间

线程共享一个内存
代码

#include <stdio.h>      // 标准输入输出库,提供printf等函数
#include <pthread.h>    // POSIX线程库,提供多线程编程接口
#include <unistd.h>

int g_data = 0;

// 线程函数 - 新线程将从此函数开始执行
void *func1(void *arg)  // 线程函数必须符合此签名:返回void*,接受一个void*参数
{
   // 打印当前线程ID和传入的参数
   printf("t1:%lu thread is created\n", (unsigned long)pthread_self()); // pthread_self()获取当前线程ID
   printf("t1:param is %d\n", *((int *)arg)); // 将void*参数转换回int*并解引用获取值

   while(1)
   {
    printf("t1:%d\n",g_data++);
    sleep(1);
   }

}

// 线程函数 - 新线程将从此函数开始执行
void *func2(void *arg)  // 线程函数必须符合此签名:返回void*,接受一个void*参数
{
   // 打印当前线程ID和传入的参数
   printf("t2:%lu thread is created\n", (unsigned long)pthread_self()); // pthread_self()获取当前线程ID
   printf("t2:param is %d\n", *((int *)arg)); // 将void*参数转换回int*并解引用获取值

  while(1)
   {
    printf("t2:%d\n",g_data++);
    sleep(1);
   }
}

int main()
{
    int ret;            // 用于存储pthread_create函数的返回值
    int param = 100;    // 要传递给线程的参数
    pthread_t t1;       // 线程标识符,用于存储新创建线程的ID
    pthread_t t2;       // 线程标识符,用于存储新创建线程的ID


    // 创建新线程
    // 参数1: &t1 - 线程标识符的地址,创建成功后这里会存储新线程的ID
    // 参数2: NULL - 线程属性,NULL表示使用默认属性
    // 参数3: func1 - 线程函数的指针,新线程将从该函数开始执行
    // 参数4: (void *)&param - 传递给线程函数的参数,需要转换为void*类型
    ret = pthread_create(&t1, NULL, func1, (void *)&param);

    // 检查线程是否创建成功
    if(ret == 0)        // pthread_create返回0表示成功
    {
        printf("main:create t1 success\n");
    }
    else
    {
        // 实际编程中应该处理错误情况
        printf("main:create t1 failed with error %d\n", ret);
        return 1;
    }

     ret = pthread_create(&t2, NULL, func2, (void *)&param);

    // 检查线程是否创建成功
    if(ret == 0)        // pthread_create返回0表示成功
    {
        printf("main:create t1 success\n");
    }
    else
    {
        // 实际编程中应该处理错误情况
        printf("main:create t1 failed with error %d\n", ret);
        return 1;
    }

    while(1)
   {
    printf("main:%d\n",g_data++);
    sleep(1);
   }

    // 打印主线程的ID
   // printf("main:%lu\n", (unsigned long)pthread_self());

    // 等待线程t1结束,并获取其返回值
    // 参数1: t1 - 要等待的线程标识符
    // 参数2: (void **)&pret - 指向指针的指针,用于接收线程的返回值
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    // 打印线程t1的返回值
   // printf("main:t1 quit:%d\n", *pret); // 解引用pret获取线程返回的值

    return 0;
}
image.png

线程同步之互斥量加锁减锁

pthread_mutex_init函数 创建互斥锁

pthread_mutex_init 是 POSIX 线程(Pthreads)库中用于动态初始化互斥锁(Mutex) 的函数。互斥锁是用于保护共享资源、避免多个线程同时访问从而导致数据竞争(Data Race)的关键同步机制。

你可以把它想象成一个房间的钥匙。只有一个线程可以持有这把“钥匙”(锁),进入“房间”(临界区代码)访问“物品”(共享数据)。其他线程必须等待钥匙被归还(锁被解锁)后才能获取它并进入房间。

pthread_mutex_init 提供了比静态初始化更灵活的方式来配置互斥锁的属性。

函数原型

#include <pthread.h>

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

参数解析

1. pthread_mutex_t *restrict mutex
  • 用途:这是一个出参。指向一个需要被初始化的互斥锁对象的指针。
  • 类型pthread_mutex_t 是一个代表互斥锁的不透明数据类型。你不需要关心它的内部结构,只需提供它的地址供函数初始化。
  • 生命周期:该互斥锁变量的内存必须由程序员分配(通常是全局变量、静态变量或堆上的结构体成员)。pthread_mutex_init 只是初始化这块内存中锁的状态。
2. const pthread_mutexattr_t *restrict attr
  • 用途:用于设置新互斥锁的属性。如果传递 NULL,则互斥锁使用默认属性创建。
  • 类型pthread_mutexattr_t 是一个不透明的属性对象,需要通过一系列函数(如 pthread_mutexattr_initpthread_mutexattr_settype 等)来配置。
  • 常见属性
    • 类型(Type):这是最重要的属性。
      • PTHREAD_MUTEX_NORMAL(默认):标准锁,不提供死锁检测。同一线程重复加锁会导致死锁。
      • PTHREAD_MUTEX_ERRORCHECK:错误检查锁。同一线程重复加锁会立即返回错误EDEADLK,便于调试。
      • PTHREAD_MUTEX_RECURSIVE:递归锁。允许同一线程对同一锁多次加锁,但必须有相同次数的解锁操作。
      • PTHREAD_MUTEX_DEFAULT:通常映射为 PTHREAD_MUTEX_NORMAL
    • 进程共享(Process-shared)PTHREAD_PROCESS_PRIVATE(默认,仅进程内线程间共享)或 PTHREAD_PROCESS_SHARED(可用于跨进程同步,锁需位于共享内存中)。
    • 健壮性(Robustness):处理锁的持有者线程终止的情况。
  • 通常做法:对于大多数简单应用,直接传递 NULL 使用默认属性(PTHREAD_MUTEX_NORMAL)就足够了。当需要递归锁或错误检查等高级特性时,才需要配置属性对象。

返回值

  • 成功:返回 0
  • 失败:返回一个错误码(非零值),而不是设置 errno。常见的错误码有:
    • EAGAIN:系统缺乏初始化另一个互斥锁所需的非内存资源。
    • ENOMEM:内存不足,无法初始化互斥锁。
    • EPERM:没有权限执行该操作。
    • EBUSY:尝试重新初始化一个已经初始化的互斥锁(行为未定义)。
    • EINVALattr 参数指定的属性无效。

pthread_mutex_destroy函数 销毁互斥锁

pthread_mutex_destroy 是 POSIX 线程(Pthreads)库中用于销毁一个互斥锁(Mutex) 的函数。它的作用是释放互斥锁可能占用的任何内部资源,并将其置于未初始化状态。

你可以将它看作是 pthread_mutex_init 的逆操作。它负责清理工作,是资源管理中的重要一环,用于防止资源泄漏。

函数原型

#include <pthread.h>

int pthread_mutex_destroy(pthread_mutex_t *mutex);

参数解析

pthread_mutex_t *mutex
  • 用途:指向一个需要被销毁的、之前已经初始化过的互斥锁对象的指针。
  • 类型pthread_mutex_t。这是一个代表互斥锁的不透明数据类型。

返回值

  • 成功:返回 0
  • 失败:返回一个错误码(非零值),而不是设置 errno。常见的错误码有:
    • EBUSY:尝试销毁一个正在被使用正在被其他线程等待的互斥锁。这是最常见、最重要的错误。
    • EINVALmutex 参数指定的值无效(例如,指向的地址无效,或者该互斥锁已经被销毁)。

pthread_mutex_lock函数 加锁 会阻塞

pthread_mutex_lock 是 POSIX 线程(Pthreads)库中最核心的同步函数之一。它的作用是对指定的互斥锁(Mutex)进行加锁操作

如果互斥锁当前未被任何线程持有(处于解锁状态),则调用线程会成功获取该锁,并继续执行。

如果互斥锁已经被另一个线程持有,则调用线程会被阻塞(Block),进入等待状态,直到该互斥锁被当前持有它的线程解锁为止。此时,该等待线程会被唤醒并成功获取锁。

它的核心目的是实现临界区(Critical Section)的互斥访问,确保同一时间只有一个线程可以执行受保护的代码段,从而防止数据竞争(Data Race)。

函数原型

#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);

参数解析

pthread_mutex_t *mutex
  • 用途:指向一个已被正确初始化的互斥锁对象的指针。
  • 类型pthread_mutex_t。这是一个代表互斥锁的不透明数据类型。

返回值

  • 成功:返回 0。意味着调用线程已成功获取(锁定)该互斥锁。
  • 失败:返回一个错误码(非零值),而不是设置 errno。常见的错误码有:
    • EINVALmutex 参数指定的值无效(例如,指向的地址无效,或者该互斥锁未被初始化)。
    • EDEADLK死锁错误。对于设置了 PTHREAD_MUTEX_ERRORCHECK 属性的互斥锁,如果同一个线程试图对已经由自己锁定的互斥锁再次加锁,则会立即返回此错误,而不会被阻塞。这是一种有用的调试机制。
    • EAGAIN:锁的递归锁次数已超出上限(适用于递归锁)。

pthread_mutex_trylock 函数 加锁 不会被阻塞

pthread_mutex_trylock 是 pthread_mutex_lock 的非阻塞(non-blocking) 版本。它的核心特点是:尝试获取互斥锁,但如果锁已经被占用,它不会让调用线程进入等待状态,而是立即返回一个错误码。

你可以把它想象成:

  • pthread_mutex_lock -> 等位吃饭:如果没空位,你就在门口一直等,直到有空位为止。
  • pthread_mutex_trylock -> 问一句“有位吗?”:如果没空位,你转身就走,不会等待,然后决定是去别家吃还是等会儿再来问问。

函数原型和返回值

#include <pthread.h>

int pthread_mutex_trylock(pthread_mutex_t *mutex);

关键在于其返回值:

  • 返回 0:成功获取了锁。此时线程可以安全地进入临界区。
  • 返回 EBUSY锁已被其他线程持有。这是它的标志性返回值,意味着“尝试失败,但我不想等”。
  • 其他错误(如 EINVAL):参数无效等错误,与 pthread_mutex_lock 类似。

pthread_mutex_unlock函数 解锁

pthread_mutex_unlock 是 POSIX 线程(Pthreads)库中与 pthread_mutex_lock 配对的核心同步函数。它的作用是释放(解锁)当前线程持有的指定互斥锁

如果还有其他线程正在等待(被阻塞在 pthread_mutex_lock 调用上)这个锁,解锁操作会唤醒其中一个等待线程,该线程随后将成功获取锁并继续执行。

你可以把它看作是将“房间的钥匙”放回原处,让其他等待的人可以取用。忘记调用这个函数是导致多线程程序中死锁(Deadlock) 的最常见原因。

函数原型
#include <pthread.h>

int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数解析
pthread_mutex_t *mutex
  • 用途:指向一个已被当前线程成功锁定(通过 pthread_mutex_lockpthread_mutex_trylock 或 pthread_mutex_timedlock)的互斥锁对象的指针。
  • 类型pthread_mutex_t。这是一个代表互斥锁的不透明数据类型。
返回值
  • 成功:返回 0。意味着调用线程已成功释放(解锁)该互斥锁。
  • 失败:返回一个错误码(非零值),而不是设置 errno。常见的错误码有:
    • EINVALmutex 参数指定的值无效(例如,指向的地址无效,或者该互斥锁未被初始化)。
    • EPERM权限错误。当前线程并不持有该互斥锁,却试图解锁它。这是一个严重的编程错误。

代码

#include <stdio.h>      // 标准输入输出库,提供printf等函数
#include <pthread.h>    // POSIX线程库,提供多线程编程接口
#include <unistd.h>

int g_data = 0;

pthread_mutex_t mutex;


// 线程函数 - 新线程将从此函数开始执行
void *func1(void *arg)  // 线程函数必须符合此签名:返回void*,接受一个void*参数
{
    int i;

    pthread_mutex_lock(&mutex);//加锁,会阻塞的
    for(i = 0; i < 5; i++)
    {
        // 打印当前线程ID和传入的参数
        printf("t1:%lu thread is created\n", (unsigned long)pthread_self()); // pthread_self()获取当前线程ID
        printf("t1:param is %d\n", *((int *)arg)); // 将void*参数转换回int*并解引用获取值
    }
    pthread_mutex_unlock(&mutex);//解锁
}

// 线程函数 - 新线程将从此函数开始执行
void *func2(void *arg)  // 线程函数必须符合此签名:返回void*,接受一个void*参数
{
    pthread_mutex_lock(&mutex);//加锁

   // 打印当前线程ID和传入的参数
   printf("t2:%lu thread is created\n", (unsigned long)pthread_self()); // pthread_self()获取当前线程ID
   printf("t2:param is %d\n", *((int *)arg)); // 将void*参数转换回int*并解引用获取值

   pthread_mutex_unlock(&mutex);//解锁
}

int main()
{
    int ret;            // 用于存储pthread_create函数的返回值
    int param = 100;    // 要传递给线程的参数
    pthread_t t1;       // 线程标识符,用于存储新创建线程的ID
    pthread_t t2;       // 线程标识符,用于存储新创建线程的ID

    pthread_mutex_init(&mutex,NULL);//创建互斥锁

    // 创建新线程
    // 参数1: &t1 - 线程标识符的地址,创建成功后这里会存储新线程的ID
    // 参数2: NULL - 线程属性,NULL表示使用默认属性
    // 参数3: func1 - 线程函数的指针,新线程将从该函数开始执行
    // 参数4: (void *)&param - 传递给线程函数的参数,需要转换为void*类型
    ret = pthread_create(&t1, NULL, func1, (void *)&param);

    // 检查线程是否创建成功
    if(ret == 0)        // pthread_create返回0表示成功
    {
        printf("main:create t1 success\n");
    }
    else
    {
        // 实际编程中应该处理错误情况
        printf("main:create t1 failed with error %d\n", ret);
        return 1;
    }

     ret = pthread_create(&t2, NULL, func2, (void *)&param);

    // 检查线程是否创建成功
    if(ret == 0)        // pthread_create返回0表示成功
    {
        printf("main:create t1 success\n");
    }
    else
    {
        // 实际编程中应该处理错误情况
        printf("main:create t1 failed with error %d\n", ret);
        return 1;
    }

    while(1)
   {
    printf("main:%d\n",g_data++);
    sleep(1);
   }

    // 打印主线程的ID
   // printf("main:%lu\n", (unsigned long)pthread_self());

    // 等待线程t1结束,并获取其返回值
    // 参数1: t1 - 要等待的线程标识符
    // 参数2: (void **)&pret - 指向指针的指针,用于接收线程的返回值
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

   pthread_mutex_destroy(&mutex);//销毁互斥锁

    return 0;
}

重点

  • 互斥锁就是相当于就一个房间,但只有一把钥匙,只有拿到钥匙的才能进去,其他想进的就要等待前面的拿钥匙回来,你才能拿钥匙进去
  • pthread_mutex_init函数 创建互斥锁
  • pthread_mutex_destroy函数 销毁互斥锁
  • pthread_mutex_lock函数 加锁 如果互斥锁已经被另一个线程持有,则调用线程会被阻塞
  • pthread_mutex_trylock 函数 加锁 但如果锁已经被占用,它不会让调用线程进入等待状态,而是立即返回一个错误码
  • pthread_mutex_unlock函数 解锁

线程条件控制实现线程的同步

pthread_cond_init函数 初始化条件变量

pthread_cond_init 是 POSIX 线程(pthreads)库中用于初始化条件变量的函数。条件变量是线程同步的一种重要机制,它允许线程在某个特定条件尚未满足时进入休眠状态,并在条件可能变为真时被唤醒,从而避免忙等待(busy-waiting),高效地协调多个线程之间的操作。

它通常与互斥锁(mutex)结合使用。

函数原型

#include <pthread.h>

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

参数解析

  1. cond (输出参数)
    • 类型pthread_cond_t *
    • 作用: 这是一个指向需要被初始化的条件变量对象的指针。你需要在调用此函数前定义一个 pthread_cond_t 类型的变量,然后将它的地址传入。
    • 示例:
pthread_cond_t my_cond;
pthread_cond_init(&my_cond, NULL); // 将 my_cond 的地址传入
  1. attr (输入参数)
    • 类型pthread_condattr_t *
    • 作用: 这是一个指向条件变量属性对象的指针。该参数用于设置条件变量的高级属性,例如是否进程间共享、设置时钟类型等。
    • 特殊值 NULL: 在绝大多数情况下,我们不需要特殊属性,只需将此参数设为 NULL。这将使用该实现默认的所有属性,这通常正是我们想要的。

返回值

  • 成功: 返回 0
  • 失败: 返回一个非零的错误码(注意,不是设置 errno)。常见的错误码包括:
    • EAGAIN: 系统缺乏初始化另一个条件变量所需的资源(非 Linux 系统上常见)。
    • ENOMEM: 内存不足,无法初始化条件变量。
    • EBUSY: 尝试重新初始化一个正在使用的条件变量(行为未定义)。
    • EINVALattr 参数指定的值无效。

使用流程

  1. 定义一个 pthread_cond_t 类型的变量。
  2. 初始化它,通常使用 pthread_cond_init
  3. 与其他线程配合使用(通常与 pthread_cond_waitpthread_cond_signalpthread_cond_broadcast 以及一个互斥锁配合)。
  4. 使用完毕后,必须调用 pthread_cond_destroy 来销毁它,释放可能占用的资源。

pthread_cond_destroy函数 销毁条件变量

pthread_cond_destroy 是 POSIX 线程(pthreads)库中的一个函数,用于销毁一个条件变量(condition variable),并释放它可能占用的任何资源。

函数原型:

#include <pthread.h>

int pthread_cond_destroy(pthread_cond_t *cond);
  • 参数: cond – 一个指向要销毁的条件变量的指针。
  • 返回值:
    • 成功时,返回 0
    • 失败时,返回一个非零的错误码(注意,它不设置 errno,而是直接返回错误码)。

pthread_cond_wait函数 等待条件函数

pthread_cond_wait 是 POSIX 线程库中用于等待条件变量的函数。它的核心作用是:让当前线程释放它所持有的互斥锁,然后进入休眠状态,等待被唤醒。当被唤醒后,该函数会重新获取互斥锁,然后返回。

它永远是和一个互斥锁(mutex) 配合使用的,用于解决“检查条件”和“进入休眠”这两个操作之间的竞态条件

函数原型

#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

参数解析

  1. cond (输入参数)
    • 类型pthread_cond_t *
    • 作用: 指向一个已经初始化好的条件变量的指针。线程将在这个条件变量上等待。
  2. mutex (输入参数)
    • 类型pthread_mutex_t *
    • 作用: 指向一个已经被当前线程锁定的互斥锁的指针。这个互斥锁用于保护“条件”本身的检查。

返回值

  • 成功: 返回 0
  • 失败: 返回一个非零的错误码。最常见的是:
    • EINVAL: 参数 cond 或 mutex 无效(例如,未初始化)。

pthread_cond_timedwait函数 延时等待条件函数

pthread_cond_timedwait 函数与 pthread_cond_wait 功能基本相同,都是原子地释放互斥锁并进入等待状态。最关键的区别在于:pthread_cond_timedwait 允许设置一个最长等待时间。如果在指定的时间到达之前,线程没有被其他线程通过 pthread_cond_signal 或 pthread_cond_broadcast 唤醒,它将自动超时唤醒并尝试重新获取互斥锁。

这个函数对于构建响应式、健壮的系统至关重要,它可以防止线程因为等不到信号而无限期阻塞,从而避免某些形式的死锁或系统无响应。

函数原型

#include <pthread.h>
#include <time.h> // 需要包含此头文件以使用 struct timespec

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

参数解析

  1. cond (输入参数)
    • 类型pthread_cond_t *
    • 作用: 指向一个已经初始化好的条件变量的指针。线程将在这个条件变量上等待。
  2. mutex (输入参数)
    • 类型pthread_mutex_t *
    • 作用: 指向一个已经被当前线程锁定的互斥锁的指针。
  3. abstime (输入参数) – 这是关键参数
    • 类型const struct timespec *
    • 作用: 指向一个 timespec 结构体,该结构体指定了等待操作的绝对时间(Absolute Time)超时点。
    • 时间结构体:
struct timespec {
            time_t tv_sec;  // 秒 (Seconds)
            long   tv_nsec; // 纳秒 (Nanoseconds) [0, 999999999]
        };
  • 绝对时间 vs. 相对时间: 这是最重要的概念。abstime 参数指定的不是“等待多长时间”,而是“等到什么时候”。例如,如果想等待 5 秒钟,你需要计算当前时间 + 5 秒,并将这个未来的时间点传入。

返回值

  • 成功且被信号唤醒: 返回 0
  • 超时: 返回 ETIMEDOUT
  • 失败: 返回其他错误码,如 EINVAL(参数无效)等。
    重要提示:无论是超时返回还是被信号唤醒返回,函数在返回前都已重新获取了互斥锁

pthread_cond_signal函数 唤醒等待特定条件变量的线程的函数

pthread_cond_signal 是 POSIX 线程(pthreads)库中用于唤醒一个正在等待特定条件变量的线程的函数。它是线程间同步和通信的核心机制之一,通常与互斥锁(mutex)和条件变量(condition variable)结合使用,实现经典的“等待-通知”模式。

函数原型:

#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);
  • 参数: cond – 一个指向要发送信号的条件变量的指针。
  • 返回值:
    • 成功时,返回 0
    • 失败时,返回一个非零的错误码。

pthread_cond_broadcast函数 唤醒所有等待的条件函数

pthread_cond_broadcast 是 POSIX 线程(pthreads)库中用于唤醒所有正在等待特定条件变量的线程的函数。它与 pthread_cond_signal 功能相似,但唤醒策略不同,是实现线程同步中“一对多”通知的关键机制。

函数原型:

#include <pthread.h>

int pthread_cond_broadcast(pthread_cond_t *cond);
  • 参数: cond – 一个指向要广播信号的条件变量的指针。
  • 返回值:
    • 成功时,返回 0
    • 失败时,返回一个非零的错误码(通常是 EINVAL)。

代码

#include <stdio.h>      // 标准输入输出库,提供printf等函数
#include <pthread.h>    // POSIX线程库,提供多线程编程接口
#include <unistd.h>
#include <stdlib.h>

int g_data = 0;

pthread_mutex_t mutex;//互斥锁
pthread_cond_t cond;//条件


// 线程函数 - 新线程将从此函数开始执行
void *func1(void *arg)  // 线程函数必须符合此签名:返回void*,接受一个void*参数
{

        // 打印当前线程ID和传入的参数
        printf("t1:%lu thread is created\n", (unsigned long)pthread_self()); // pthread_self()获取当前线程ID
        printf("t1:param is %d\n", *((int *)arg)); // 将void*参数转换回int*并解引用获取值

        static int cnt = 0;

        while (1)
        {
            pthread_cond_wait(&cond, &mutex);//等待条件满足,不满足就在休眠
            printf("t1vrun=============================\n");

            printf("t1:%d\n", g_data);
            g_data = 0;
            sleep(1);
            if(cnt++ == 10)
            {
                exit(1);
            }
        }

}

// 线程函数 - 新线程将从此函数开始执行
void *func2(void *arg)  // 线程函数必须符合此签名:返回void*,接受一个void*参数
{
   // 打印当前线程ID和传入的参数
   printf("t2:%lu thread is created\n", (unsigned long)pthread_self()); // pthread_self()获取当前线程ID
   printf("t2:param is %d\n", *((int *)arg)); // 将void*参数转换回int*并解引用获取值

    while(1)
    {
        printf("t2:%d\n", g_data);
        pthread_mutex_lock(&mutex);//加锁
        g_data++;
        if(g_data == 3)
        {
            pthread_cond_signal(&cond);//唤醒等待条件
        }
        pthread_mutex_unlock(&mutex);//解锁
        sleep(1);
    }

}

int main()
{
    int ret;            // 用于存储pthread_create函数的返回值
    int param = 100;    // 要传递给线程的参数
    pthread_t t1;       // 线程标识符,用于存储新创建线程的ID
    pthread_t t2;       // 线程标识符,用于存储新创建线程的ID

    pthread_mutex_init(&mutex,NULL);//创建互斥锁
    pthread_cond_init(&cond, NULL);//条件初始化


    // 创建新线程
    // 参数1: &t1 - 线程标识符的地址,创建成功后这里会存储新线程的ID
    // 参数2: NULL - 线程属性,NULL表示使用默认属性
    // 参数3: func1 - 线程函数的指针,新线程将从该函数开始执行
    // 参数4: (void *)&param - 传递给线程函数的参数,需要转换为void*类型
    ret = pthread_create(&t1, NULL, func1, (void *)&param);

    // 检查线程是否创建成功
    if(ret == 0)        // pthread_create返回0表示成功
    {
        //printf("main:create t1 success\n");
    }
    else
    {
        // 实际编程中应该处理错误情况
        //printf("main:create t1 failed with error %d\n", ret);
       // return 1;
    }

     ret = pthread_create(&t2, NULL, func2, (void *)&param);//创建进程

    // 检查线程是否创建成功
    if(ret == 0)        // pthread_create返回0表示成功
    {
        //printf("main:create t1 success\n");
    }
    else
    {
        // 实际编程中应该处理错误情况
       // printf("main:create t1 failed with error %d\n", ret);
      //  return 1;
    }



    // 打印主线程的ID
   // printf("main:%lu\n", (unsigned long)pthread_self());

    // 等待线程t1结束,并获取其返回值
    // 参数1: t1 - 要等待的线程标识符
    // 参数2: (void **)&pret - 指向指针的指针,用于接收线程的返回值
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    pthread_mutex_destroy(&mutex);//销毁互斥锁
    pthread_cond_destroy(&cond);//销毁条件

    return 0;
}

重点

  • pthread_cond_init函数 初始化条件变量
  • pthread_cond_destroy函数 销毁条件变量
  • pthread_cond_wait函数 等待条件函数
  • pthread_cond_timedwait函数 延时等待条件函数
  • pthread_cond_signal函数 唤醒等待特定条件变量的线程的函数
  • pthread_cond_broadcast函数 唤醒所有等待的条件函数
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇