目录
(一)POSIX线程库
(二)创建线程
2.1 线程ID及进程地址空间布局
(三)线程终止
(四)分离线程
(一)POSIX线程库
POSIX线程库(POSIX Threads Library),通常简称为Pthreads,是一个为POSIX操作系统(包括UNIX和类UNIX系统如Linux)提供线程支持的库。Pthreads定义了一组API,允许程序员创建、管理、同步和销毁线程。
- 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
- 要使用这些函数库,要通过引入头文<pthread.h>
- 链接这些线程函数库时要使用编译器命令的“-lpthread”选项
(二)创建线程
【函数介绍】
功能:创建一个新的线程
原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void * (*start_routine)(void*), void *arg);
参数
thread:返回线程ID
attr:设置线程的属性,attr为NULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码
错误检查:
-
对于Pthreads函数,如果调用失败,通常会返回一个非零的错误代码,你可以使用这个错误代码来查询具体是什么错误发生了。为了将错误代码映射到人类可读的错误消息,你可以使用
strerror
或pthread_strerror
函数(如果可用)。 -
另外,Pthreads库确实为每个线程提供了一个私有的errno变量,这允许线程安全地访问errno。然而,对于Pthreads函数本身来说,直接检查返回值通常是更好的做法,因为这样可以避免任何可能的线程间干扰,并且通常更加高效。
【代码示例】
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;
int g_val = 0;
void *threadRun1(void *args)
{
while (true)
{
sleep(1);
cout << "t1 thread..." << getpid() << " &g_val: " << &g_val << " , g_val: " << g_val << endl;
}
}
void *threadRun2(void *args)
{
while (true)
{
sleep(1);
cout << "t2 thread..." << getpid() << " &g_val: " << &g_val << " , g_val: " << g_val++ << endl;
}
}
int main()
{
pthread_t t1, t2;
pthread_create(&t1, nullptr, threadRun1, nullptr);
pthread_create(&t1, nullptr, threadRun2, nullptr);
while (true)
{
sleep(1);
cout << "main thread..." << getpid() << " &g_val: " << &g_val << " , g_val: " << g_val << endl;
}
}
2.1 线程ID及进程地址空间布局
- pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。
- 前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
- pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
- 线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID:
【函数介绍】
pthread_t pthread_self(void);
错误检查:
- 如果成功,
pthread_self
返回调用线程的线程 ID。如果发生错误,这个函数没有定义错误码返回机制,因为它总是应该成功。
【代码示例】
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
void* threadRun(void* arg) {
pthread_t id = pthread_self(); // 获取当前线程的 ID
cout << "the thread ID is : " << id << endl;
sleep(1);
return nullptr;
}
int main() {
pthread_t tid;
int ret;
// 创建线程
ret = pthread_create(&tid, nullptr, threadRun, nullptr);
if (ret != 0) {
cerr << "Error: pthread_create() failed with error " << ret << endl;
return 1;
}
// 等待线程完成
pthread_join(tid, nullptr);
cout << "Main thread exiting." << endl;
return 0;
}
pthread_t 到底是什么类型呢?取决于实现。对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。
(三)线程终止
- 1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
#define NUM 10
void *threadrun(void *args)
{
char *name = (char*)args;
while (true)
{
cout << "new thread run, the new thread name is : " << name << endl;
sleep(3);
break;
}
delete name;
return nullptr;
}
int main()
{
pthread_t tids[NUM];
for(int i= 0; i< NUM; ++i){
char *tname = new char[64];
snprintf(tname,64,"thread-%d",i+1);
pthread_create(tids+i,nullptr,threadrun,tname);
}
void *ret = nullptr;
for(int i= 0; i< NUM; ++i){
int n = pthread_join(tids[i],nullptr);
if(n != 0) cerr << "pthread_join error" << endl;
cout << "thread quit: " <<(uint64_t)ret << endl;
}
cout << "all thread quit..."<<endl;
return 0;
}
- 2. 线程可以调用pthread_ exit终止自己。
【函数介绍】
功能:线程终止
原型
void pthread_exit(void *value_ptr);
参数
value_ptr:value_ptr不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
- 需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
【代码示例】
#define NUM 10
void *threadrun(void *args)
{
char *name = (char*)args;
while (true)
{
cout << "new thread run, the new thread name is : " << name << endl;
sleep(3);
break;
}
delete name;
pthread_exit((void*)1);
}
int main()
{
pthread_t tids[NUM];
for(int i= 0; i< NUM; ++i){
char *tname = new char[64];
snprintf(tname,64,"thread-%d",i+1);
pthread_create(tids+i,nullptr,threadrun,tname);
}
void *ret = nullptr;
for(int i= 0; i< NUM; ++i){
int n = pthread_join(tids[i],nullptr);
if(n != 0) cerr << "pthread_join error" << endl;
cout << "thread quit: " <<(uint64_t)ret << endl;
}
cout << "all thread quit..."<<endl;
return 0;
}
- 3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。
【函数介绍】
功能:取消一个执行中的线程
原型
int pthread_cancel(pthread_t thread);
参数
thread:线程ID
返回值:成功返回0;失败返回错误码
【代码示例】
void *threadRun(void* args)
{
const char*name = static_cast<const char *>(args);
int cnt = 5;
while(cnt)
{
cout << name << " is running: " << cnt-- << " obtain self id: " << pthread_self() << endl;
sleep(1);
}
pthread_exit((void*)11);
// PTHREAD_CANCELED; #define PTHREAD_CANCELED ((void *) -1)
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, threadRun, (void*)"thread 1");
sleep(3);
pthread_cancel(tid);
void *ret = nullptr;
pthread_join(tid, &ret);
cout << " new thread exit : " << (int64_t)ret << "quit thread: " << tid << endl;
return 0;
}
【输出结果】(三秒后取消操作)
其次就是在上述代码中,使用了线程等待函数,那为什么需要线程等待?
- 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
- 创建新的线程不会复用刚才退出线程的地址空间。
【函数介绍】
功能:等待线程结束
原型
int pthread_join(pthread_t thread, void **value_ptr);
参数
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码
- 1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
- 2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_ CANCELED。
- 3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
- 4. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。
(四)分离线程
- 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
- 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
【函数介绍】
int pthread_detach(pthread_t thread);
【代码示例】
void* threadRun(void* args) {
sleep(2);
cout << "线程任务已完成\n";
return nullptr;
}
int main() {
pthread_t tid;
int ret;
// 创建线程
ret = pthread_create(&tid, nullptr, threadRun, nullptr);
if (ret) {
cerr << "Error: pthread_create() failed\n";
return 1;
}
// 分离线程
pthread_detach(tid);
// 主线程继续执行
cout << "主线程继续执行...\n";
// 主线程休眠一段时间以便观察分离线程的输出
sleep(3);
// 主线程结束,分离线程的资源会在其完成后由系统自动回收
return 0;
}