Linux操作系统6-线程2线程的创建,终止,等待与退出
Linux操作系统6- 线程2(线程的创建,终止,等待与退出)
上篇文章:
本篇Gitee仓库:[myLerningCode/l28 · 橘子真甜/Linux操作系统与网络编程学习 - 码云 - 开源中国 (gitee.com)]( learning/tree/master/myLerningCode/l28 “myLerningCode/l28 · 橘子真甜/Linux操作系统与网络编程学习 - 码云 - 开源中国 (gitee.com)”) 进程:承担操作系统分配资源的基本单位 线程:CPU调度的基本单位,是进程内的执行流。在Linux中,使用轻量级进程模拟线程 本文的线程操作都是POSIX线程控制库。链接这些线程库需要在编译的时候加上 -lpthread
一. 线程创建pthread_create
调用接口原型
//所需头文件
#include
//用于创建一个线程并执行对应函数
int pthread_create(pthread_t* thread, const pthread_attr_t attr, void(start_rountine)(void), void* args);
// thread 输入输出型参数,表示创造线程的tid
// attr 用于控制线程的属性,一般设置为nullptr
// start_rountine 一个函数指针,表示要线程执行的函数
// args 一个void参数,线程创建后,将这个参数传递给start_routine的参数列表
//成功返回0,失败返回-1,并且设置错误码
args参数是void类型的,所以我们能够传递任意参数让其接收。
测试代码:
我们使用vector保存线程的tid,同时创建一批线程,并且通过arg参数传递一个string类型作为线程的名字
#include
#include
#include
#include
int gnum = 5;
void *start_routine(void *args)
{
std::string name = static_cast(args);
while (true)
{
std::cout « “我是新线程,我的名字是” « name « “pid为:” « getpid() « std::endl;
sleep(1);
}
}
int main()
{
std::vector tids(gnum);
for (int i = 0; i < tids.size(); ++i)
{
std::string name = “thread” + std::to_string(i);
pthread_create(&tids[i], nullptr, start_routine, (void *)name.c_str());
}
while (true)
{
std::cout « “我是主线程,我的pid为:” « getpid() « std::endl;
sleep(1);
}
return 0;
}
测试结果如下:
可以看到,我们创建了4个新线程,它们的pid相同。但是它们的名字为何没有0,2和3呢?
这是因为我们的name是同一个name,销毁后,线程仍访问这块销毁空间。
所以这里我们需要定义一个结构体来保存数据。
示例代码:
#include
#include
#include
#include
int gnum = 5;
struct ThreadData
{
pthread_t tid;
std::string name;
};
void *start_routine(void *args)
{
ThreadData *td = static_cast(args);
while (true)
{
std::cout « “我是新线程,我的名字是:” « td->name « " pid为:" « getpid() « std::endl;
sleep(1);
}
}
int main()
{
std::vector tids(gnum);
for (int i = 0; i < tids.size(); ++i)
{
// 在堆中创建数据
ThreadData *td = new ThreadData();
td->name = “Thread " + std::to_string(i);
pthread_create(&td->tid, nullptr, start_routine, (void *)td);
tids[i] = td;
}
while (true)
{
std::cout « “我是主线程,我的pid为:” « getpid() « " " « std::endl;
sleep(1);
}
return 0;
}
测试结果如下:
如果在线程的执行流中定义变量,这些变量是线程独有的
修改start_routine
void *start_routine(void *args)
{
int cnt = 10;
ThreadData *td = static_cast(args);
while (true)
{
std::cout « “我是新线程,我的名字是:” « td->name « “cnt:” « cnt– « “cnt 地址为:” « &cnt « std::endl;
sleep(1);
}
}
运行结果如下:可以看到,每一个线程中的cnt的地址都是不一样的。这说明每一个线程都有自己独立的栈资源
二. 线程的终止
如果线程调用exit()会导致整个线程终止。
测试代码如下:在cnt为5的时候,线程调用exit进行终止
#include
#include
#include
#include
int gnum = 5;
struct ThreadData
{
pthread_t tid;
std::string name;
};
void *start_routine(void *args)
{
int cnt = 10;
ThreadData *td = static_cast(args);
while (true)
{
if (cnt == 5)
exit(1);
std::cout « “我是新线程,我的名字是:” « td->name « " cnt:” « cnt– « " cnt 地址为:" « &cnt « std::endl;
sleep(1);
}
}
int main()
{
std::vector tids(gnum);
for (int i = 0; i < tids.size(); ++i)
{
// 在堆中创建数据
ThreadData *td = new ThreadData();
td->name = “Thread " + std::to_string(i);
pthread_create(&td->tid, nullptr, start_routine, (void *)td);
tids[i] = td;
}
while (true)
{
std::cout « “我是主线程,我的pid为:” « getpid() « " " « std::endl;
sleep(1);
}
return 0;
}
运行结果如下:
可以看到,线程调用exit会导致整个进程退出。exit是用于进程终止,而不是用于某一个线程的终止。
线程终止是当线程的函数执行完毕就会自动退出。
修改start_routine:当cnt为0的时候,退出循环。以返回的形式终止线程
void *start_routine(void *args)
{
int cnt = 10;
ThreadData *td = static_cast(args);
while (cnt–)
{
std::cout « “我是新线程,我的名字是:” « td->name « " cnt:” « cnt « " cnt 地址为:" « &cnt « std::endl;
sleep(1);
}
return nullptr;
}
运行结果如下:
2.1 pthread_exit
可以通过pthread_exit退出线程。效果与return是一样的 //函数原型 void pthread_exit(void* retval) pthread_exit的效果与函数内使用return的效果是一样的,这里就不过多解释
三. 线程的等待与退出
与进程等待一样,线程也需要等待。如果不等待线程,线程对应的pcb可能不会被释放。就会造成类似僵尸进程一样的资源泄露。
线程等待的作用
1 等待线程退出的资源与信息(通过 return 的返回值获取)
2 回收线程的pcb与内核资源防止资源泄露
****当然我们可以不获取线程退出的信息,但是必须收回线程的资源
3.1 pthread_join
pthread_join用于主线程等待新线程的退出。通过输入输出型参数,可以获取主线程退出的信息和需要传递给主线程的资源。同时会回收新线程的pcb和内核资源。
//所需头文件
#include
//函数原型
int pthread_join(pthread_t thread, void** retval)
//参数说明
thread:需要等待的线程
retval:输入输出型参数,通过指针的方法来获取我们的退出值
测试代码:主线程一次性等待所有的新线程
#include
#include
#include
#include
int gnum = 5;
struct ThreadData
{
pthread_t tid;
std::string name;
};
void *start_routine(void *args)
{
int cnt = 10;
ThreadData *td = static_cast(args);
while (cnt–)
{
std::cout « “我是新线程,我的名字是:” « td->name « " cnt:" « cnt « " cnt 地址为:" « &cnt « std::endl;
sleep(1);
}
return nullptr;
}
int main()
{
std::vector tds(gnum);
for (int i = 0; i < tds.size(); ++i)
{
// 在堆中创建数据
ThreadData *td = new ThreadData();
td->name = “Thread " + std::to_string(i);
pthread_create(&td->tid, nullptr, start_routine, (void *)td);
tds[i] = td;
}
for (const auto &td : tds)
{
int n = pthread_join(td->tid, nullptr); // 使用bullptr表示不关心线程退出的状态与信息
if (n != 0)
std::cout « “线程退出失败” « std::endl;
std::cout « “主线程成功等待新线程:” « td->name « std::endl;
}
while (true)
{
std::cout « “我是主线程,我的pid为:” « getpid() « " " « std::endl;
sleep(1);
}
return 0;
}
测试结果如下:可以看到,主线程成功等待了所有等待新线程
3.2 线程退出信息
无论是return还是调用pthread_join退出的信息都是 void*retval。pthread_join通过输入输出型参数
voiddretval来获取退出信息。**
测试代码如下:
#include
#include
#include
#include
int gnum = 5;
struct ThreadData
{
pthread_t tid;
std::string name;
};
struct ThreadReturn
{
int exit_code;
int exit_result;
};
void *start_routine(void *args)
{
int cnt = 10;
ThreadData *td = static_cast(args);
while (cnt–)
{
std::cout « “我是新线程,我的名字是:” « td->name « " cnt:” « cnt « " cnt 地址为:" « &cnt « std::endl;
sleep(1);
}
ThreadReturn *tr = new ThreadReturn(); // 堆中创建的数据才能正常返回
tr->exit_code = 0;
tr->exit_result = td->tid;
return (void *)tr; // 退出线程的信息结构体
}
int main()
{
std::vector tds(gnum);
for (int i = 0; i < tds.size(); ++i)
{
// 在堆中创建数据
ThreadData *td = new ThreadData();
td->name = “Thread " + std::to_string(i);
pthread_create(&td->tid, nullptr, start_routine, (void *)td);
tds[i] = td;
}
for (const auto &td : tds)
{
ThreadReturn *tr; // 用于获取线程退出的信息
int n = pthread_join(td->tid, (void **)&tr); // 使用bullptr表示不关心线程退出的状态与信息
if (n != 0)
std::cout « “线程退出失败” « std::endl;
std::cout « “主线程成功等待新线程:” « td->name;
std::cout « " 该线程退出大队信息为 " « “exitcode:” « tr->exit_code « " exit_result:” « tr->exit_result « std::endl;
}
while (true)
{
std::cout « “我是主线程,我的pid为:” « getpid() « " " « std::endl;
sleep(1);
}
return 0;
}
测试结果如下:
在进程退出中,我们会关心进程的退出码和退出信号。
而线程不用关心异常信号,因为一旦出现异常信号整个进程都会退出。