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)。
- 等等。
- 分离状态(detached state):
- 通常做法:对于大多数简单应用,直接传递
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
:系统资源不足,无法创建另一个线程,或线程数量超过了系统的限制。EINVAL
:attr
参数无效。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
。 - 重要警告:绝对不能返回一个指向线程局部变量(栈上变量)的指针! 因为当线程退出时,它的栈空间会被销毁,这个指针就会变成野指针。返回的数据必须在:
- 全局数据区(如全局变量、静态变量)。
- 堆上(用
malloc
,calloc
,new
等分配)。 - 或者是一个常数值(通常需要强制转换为
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 *)¶m - 传递给线程函数的参数,需要转换为void*类型
ret = pthread_create(&t1, NULL, func1, (void *)¶m);
// 检查线程是否创建成功
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 *)¶m - 传递给线程函数的参数,需要转换为void*类型
ret = pthread_create(&t1, NULL, func1, (void *)¶m);
// 检查线程是否创建成功
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 *)¶m);
// 检查线程是否创建成功
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;
}
线程同步之互斥量加锁减锁
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_init
,pthread_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):处理锁的持有者线程终止的情况。
- 类型(Type):这是最重要的属性。
- 通常做法:对于大多数简单应用,直接传递
NULL
使用默认属性(PTHREAD_MUTEX_NORMAL
)就足够了。当需要递归锁或错误检查等高级特性时,才需要配置属性对象。
返回值
- 成功:返回
0
。 - 失败:返回一个错误码(非零值),而不是设置
errno
。常见的错误码有:EAGAIN
:系统缺乏初始化另一个互斥锁所需的非内存资源。ENOMEM
:内存不足,无法初始化互斥锁。EPERM
:没有权限执行该操作。EBUSY
:尝试重新初始化一个已经初始化的互斥锁(行为未定义)。EINVAL
:attr
参数指定的属性无效。
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
:尝试销毁一个正在被使用或正在被其他线程等待的互斥锁。这是最常见、最重要的错误。EINVAL
:mutex
参数指定的值无效(例如,指向的地址无效,或者该互斥锁已经被销毁)。
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
。常见的错误码有:EINVAL
:mutex
参数指定的值无效(例如,指向的地址无效,或者该互斥锁未被初始化)。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_lock
,pthread_mutex_trylock
或pthread_mutex_timedlock
)的互斥锁对象的指针。 - 类型:
pthread_mutex_t
。这是一个代表互斥锁的不透明数据类型。
返回值
- 成功:返回
0
。意味着调用线程已成功释放(解锁)该互斥锁。 - 失败:返回一个错误码(非零值),而不是设置
errno
。常见的错误码有:EINVAL
:mutex
参数指定的值无效(例如,指向的地址无效,或者该互斥锁未被初始化)。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 *)¶m - 传递给线程函数的参数,需要转换为void*类型
ret = pthread_create(&t1, NULL, func1, (void *)¶m);
// 检查线程是否创建成功
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 *)¶m);
// 检查线程是否创建成功
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);
参数解析
cond
(输出参数)- 类型:
pthread_cond_t *
- 作用: 这是一个指向需要被初始化的条件变量对象的指针。你需要在调用此函数前定义一个
pthread_cond_t
类型的变量,然后将它的地址传入。 - 示例:
- 类型:
pthread_cond_t my_cond;
pthread_cond_init(&my_cond, NULL); // 将 my_cond 的地址传入
attr
(输入参数)- 类型:
pthread_condattr_t *
- 作用: 这是一个指向条件变量属性对象的指针。该参数用于设置条件变量的高级属性,例如是否进程间共享、设置时钟类型等。
- 特殊值
NULL
: 在绝大多数情况下,我们不需要特殊属性,只需将此参数设为NULL
。这将使用该实现默认的所有属性,这通常正是我们想要的。
- 类型:
返回值
- 成功: 返回
0
。 - 失败: 返回一个非零的错误码(注意,不是设置
errno
)。常见的错误码包括:EAGAIN
: 系统缺乏初始化另一个条件变量所需的资源(非 Linux 系统上常见)。ENOMEM
: 内存不足,无法初始化条件变量。EBUSY
: 尝试重新初始化一个正在使用的条件变量(行为未定义)。EINVAL
:attr
参数指定的值无效。
使用流程
- 定义一个
pthread_cond_t
类型的变量。 - 初始化它,通常使用
pthread_cond_init
。 - 与其他线程配合使用(通常与
pthread_cond_wait
,pthread_cond_signal
,pthread_cond_broadcast
以及一个互斥锁配合)。 - 使用完毕后,必须调用
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);
参数解析
cond
(输入参数)- 类型:
pthread_cond_t *
- 作用: 指向一个已经初始化好的条件变量的指针。线程将在这个条件变量上等待。
- 类型:
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);
参数解析
cond
(输入参数)- 类型:
pthread_cond_t *
- 作用: 指向一个已经初始化好的条件变量的指针。线程将在这个条件变量上等待。
- 类型:
mutex
(输入参数)- 类型:
pthread_mutex_t *
- 作用: 指向一个已经被当前线程锁定的互斥锁的指针。
- 类型:
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 *)¶m - 传递给线程函数的参数,需要转换为void*类型
ret = pthread_create(&t1, NULL, func1, (void *)¶m);
// 检查线程是否创建成功
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 *)¶m);//创建进程
// 检查线程是否创建成功
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函数 唤醒所有等待的条件函数