[Linux]多线程(二)原生线程库---pthread库的使用
@水墨不写bug

文章目录
- 一、pthread原生线程库的使用
-
- [1. pthread_create](#1. pthread_create)
- 全面的看待线程返回值
- [2. pthread_join](#2. pthread_join)
- [3. pthread_exit](#3. pthread_exit)
- 对比理解线程退出?
- 理解线程分离?
- [4. pthread_detach](#4. pthread_detach)
- [5. 互斥锁(Mutex)函数](#5. 互斥锁(Mutex)函数)
-
- pthread_mutex_init
- [pthread_mutex_lock / pthread_mutex_unlock](#pthread_mutex_lock / pthread_mutex_unlock)
- pthread_mutex_destroy
- [6. 条件变量(Condition Variable)函数](#6. 条件变量(Condition Variable)函数)
-
- pthread_cond_init
- pthread_cond_wait
- [pthread_cond_signal / pthread_cond_broadcast](#pthread_cond_signal / pthread_cond_broadcast)
- [7. 线程属性函数](#7. 线程属性函数)
-
- [pthread_attr_init / pthread_attr_destroy](#pthread_attr_init / pthread_attr_destroy)
- 设置分离状态
- 其他函数
- C++11的线程库是对pthread原生线程库的封装
一、pthread原生线程库的使用
1. pthread_create
作用 :创建新线程。
函数原型:
cint pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
参数:
thread
:输出型参数,存储新线程的标识符(pthread_t
类型)。attr
:线程属性(如栈大小、分离状态),若为NULL
则使用默认属性。start_routine
:线程入口函数,必须为void* (*)(void*)
类型。arg
:传递给入口函数的参数(void*
类型);全面的看待线程返回值
传递进来的参数并不一定是一个,也可以是一个ThreadData类,提前在这个类中设置好要使用的参数,那么我们就可以给新线程传递多个参数,甚至方法了!!把这个类当做一个用于线程传参的辅助类。
以及新线程执行的函数的返回值也可以是一个类:ThreadResult,把这个类看做是用于传递返回值的辅助类。 在主线程创建一个ThreadResult* 的对象tr,(void* *)&tr 即可获取返回值。
此外,主线程的传递给新线程的参数最好是堆区创建的变量:
如果是栈区的变量,创建第二个线程如果还用这个变量,也会对第一个新线程产生影响
如果是堆区的变量,那么传递给新线程之后,就相当于把这个堆区指针交给新线程维护了
返回值:
- 成功返回
0
,失败返回错误码(非errno
,需用strerror
转换)。注意事项:
- 线程创建后立即执行,需同步共享数据。
- 确保传递的
arg
指针在子线程中有效(避免悬垂指针)。- 默认创建的线程是"可连接的"(需要
pthread_join
回收资源)。
如何创建多线程?
通过循环,但是要注意,需要每次都从堆区新申请空间,而不能使用栈区空间:
例子一:
例子二:
2. pthread_join
一旦我们通过pthread_create 创建了一个线程,这个线程与主线程谁先执行是不确定的(因为线程复用的进程的调度算法,父子进程谁先执行是不确定的 ),但是一般我们希望主线程最后退出,因为主线程需要回收新线程的资源;如果给新线程安排的任务没有完成主线程就退出了,那么新线程也一并退出(这种情况不可能出现,因为新线程的任务必须要完成)。新线程需要主线程调用pthread_join回收。
作用 :等待线程终止,并回收其资源。
函数原型:
cint pthread_join(pthread_t thread, void **retval);
参数:
thread
:目标线程的标识符。retval
:输出参数,接收线程的返回值(若为NULL
则忽略返回值)。
retval是二级指针的原因是新线程的返回值是void* 类型的指针,如果想要通过传参获取这个返回值,就需要void* 的地址,也就是void**。返回值:
- 成功返回
0
,失败返回错误码。注意事项:
- 只能对非分离(
joinable
)线程调用,否则返回EINVAL
。- 调用这个函数调用线程会阻塞,直到目标线程终止。
- 必须调用
pthread_join
或pthread_detach
避免资源泄漏,否则会出现类似于僵尸进程的线程数据结构未被释放。
3. pthread_exit
作用 :终止当前线程,并传递返回值。
函数原型:
cvoid pthread_exit(void *retval);
参数:
retval
:线程的返回值(可为NULL
)。注意事项:
- 主线程调用
pthread_exit
不会终止进程,其他线程继续运行。- 若线程未分离,返回值需由其他线程通过
pthread_join
获取。- 不要在线程中返回指向局部变量的指针(栈内存会被销毁回收)。
对比理解线程退出?
在了解线程之前,我们知道的进程退出的方法有:1、return 2、exit()。
但是在有了线程的概念之后,我们需要读线程进行更加细致的区分。以及对于线程退出,pthread库提供了多个方法。
1、return退出
主线程return就是进程结束;新线程return代表这个线程退出,返回的就是void* 类型的指针。
2、调用C库函数exit()退出
主线程和新线程都一样,只要调用eixt函数,代表整个进程退出。
3、调用pthread库函数pthread_exit()退出
对于新线程而言,调用pthread_exit就相当于return,并且pthread_eixt参数也和return的返回值相同,也是void* 指针。
对于主线程而言,调用pthread_exit函数,如果新线程没有退出,则阻塞等待新线程退出;如果新线程已经退出,则直接退出。
4、被pthread_cancel取消
c
int pthread_cancel(pthread_t thread);
一般通过主线程取消其他线程(用其他线程取消主线程,主线程退出,其他线程一并退出,这个结果是未定义的)。如果一个线程被取消了,那么这个线程的返回值是-1,这代表什么意思?
在pthread库中,定义了PTHREAD_CANCELED宏:
c
#define PTHREAD_CANCELED ((void*)-1)
这个宏标识了被取消的线程是通过pthread_cancel取消结束的。
运行结果:
理解线程分离?
如果一个线程不想被主线程回收,可以调用pthread_detach(pthead_self())或者主线程调用pthread_detach(新线程tid)。调用之后,这个线程还是属于进程内部,但是主线程可以专注于做自己的事情,而不再需要回收新线程。新线程调用之后,执行完之后直接退出并回收资源。
而一个分离的线程如果出错,OS仍然是向进程发送信号,让整个进程退出。
4. pthread_detach
作用 :将线程标记为分离状态,线程终止后自动释放资源。
函数原型:
cint pthread_detach(pthread_t thread);
参数:
thread
:目标线程的标识符。返回值:
- 成功返回
0
,失败返回错误码。注意事项:
- 分离线程无法再被
pthread_join
。- 可在线程内部调用
pthread_detach(pthread_self())
自我分离。
5. 互斥锁(Mutex)函数
作用:保护共享资源,防止竞态条件。
pthread_mutex_init
cint pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
- 初始化互斥锁,
attr
为属性(NULL
表示默认)。pthread_mutex_lock / pthread_mutex_unlock
cint pthread_mutex_unlock(pthread_mutex_t *mutex);
- 加锁(阻塞)和解锁。
pthread_mutex_destroy
cint pthread_mutex_destroy(pthread_mutex_t *mutex);
- 销毁互斥锁,释放资源。
注意事项:
- 静态初始化可用
PTHREAD_MUTEX_INITIALIZER
。- 确保每次加锁后解锁,避免死锁。
- 锁的持有时间应尽量短,减少性能影响。
6. 条件变量(Condition Variable)函数
作用:线程间通知机制,需与互斥锁配合使用。
pthread_cond_init
cint pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
- 初始化条件变量。
pthread_cond_wait
cint pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
- 释放
mutex
并等待条件变量,被唤醒后重新获得锁。pthread_cond_signal / pthread_cond_broadcast
cint pthread_cond_signal(pthread_cond_t *cond); // 唤醒至少一个线程 int pthread_cond_broadcast(pthread_cond_t *cond); // 唤醒所有线程
注意事项:
- 使用循环检查条件,防止虚假唤醒。
- 调用
pthread_cond_wait
前必须持有mutex
。
7. 线程属性函数
作用:设置线程属性(如分离状态、栈大小)。
pthread_attr_init / pthread_attr_destroy
cint pthread_attr_destroy(pthread_attr_t *attr);
- 初始化和销毁属性对象。
设置分离状态
cpthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
- 创建线程时直接设置为分离状态。
其他函数
pthread_self()
:获取当前线程ID。pthread_equal(t1, t2)
:比较线程ID是否相等。
C++11的线程库是对pthread原生线程库的封装
在不同的语言中,可能会有不同的线程库,在Linux下,不同线程库都是对原生线程库的封装。而C++也不例外,在C++11引入的thread库是对pthread库的封装。
完~
转载请注明出处