上篇文章:Linux操作系统6- 线程1(线程基础,调用接口,线程优缺点)-CSDN博客
本篇Gitee仓库:myLerningCode/l28 · 橘子真甜/Linux操作系统与网络编程学习 - 码云 - 开源中国 (gitee.com)
进程:承担操作系统分配资源的基本单位
线程:CPU调度的基本单位,是进程内的执行流。在Linux中,使用轻量级进程模拟线程
本文的线程操作都是POSIX线程控制库。链接这些线程库需要在编译的时候加上 -lpthread
目录
一. 线程创建pthread_create
调用接口原型
//所需头文件
#include <pthread.h>
//用于创建一个线程并执行对应函数
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 <iostream>
#include <vector>
#include <unistd.h>
#include <pthread.h>
int gnum = 5;
void *start_routine(void *args)
{
std::string name = static_cast<const char *>(args);
while (true)
{
std::cout << "我是新线程,我的名字是" << name << "pid为:" << getpid() << std::endl;
sleep(1);
}
}
int main()
{
std::vector<pthread_t> 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 <iostream>
#include <vector>
#include <unistd.h>
#include <pthread.h>
int gnum = 5;
struct ThreadData
{
pthread_t tid;
std::string name;
};
void *start_routine(void *args)
{
ThreadData *td = static_cast<ThreadData *>(args);
while (true)
{
std::cout << "我是新线程,我的名字是:" << td->name << " pid为:" << getpid() << std::endl;
sleep(1);
}
}
int main()
{
std::vector<ThreadData *> 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<ThreadData *>(args);
while (true)
{
std::cout << "我是新线程,我的名字是:" << td->name << "cnt:" << cnt-- << "cnt 地址为:" << &cnt << std::endl;
sleep(1);
}
}
运行结果如下:可以看到,每一个线程中的cnt的地址都是不一样的。这说明每一个线程都有自己独立的栈资源
二. 线程的终止
如果线程调用exit()会导致整个线程终止。
测试代码如下:在cnt为5的时候,线程调用exit进行终止
#include <iostream>
#include <vector>
#include <unistd.h>
#include <pthread.h>
int gnum = 5;
struct ThreadData
{
pthread_t tid;
std::string name;
};
void *start_routine(void *args)
{
int cnt = 10;
ThreadData *td = static_cast<ThreadData *>(args);
while (true)
{
if (cnt == 5)
exit(1);
std::cout << "我是新线程,我的名字是:" << td->name << " cnt:" << cnt-- << " cnt 地址为:" << &cnt << std::endl;
sleep(1);
}
}
int main()
{
std::vector<ThreadData *> 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<ThreadData *>(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 <pthread.h>
//函数原型
int pthread_join(pthread_t thread, void** retval)
//参数说明
thread:需要等待的线程
retval:输入输出型参数,通过指针的方法来获取我们的退出值
测试代码:主线程一次性等待所有的新线程
#include <iostream>
#include <vector>
#include <unistd.h>
#include <pthread.h>
int gnum = 5;
struct ThreadData
{
pthread_t tid;
std::string name;
};
void *start_routine(void *args)
{
int cnt = 10;
ThreadData *td = static_cast<ThreadData *>(args);
while (cnt--)
{
std::cout << "我是新线程,我的名字是:" << td->name << " cnt:" << cnt << " cnt 地址为:" << &cnt << std::endl;
sleep(1);
}
return nullptr;
}
int main()
{
std::vector<ThreadData *> 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通过输入输出型参数 voidd**retval来获取退出信息。
测试代码如下:
#include <iostream>
#include <vector>
#include <unistd.h>
#include <pthread.h>
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<ThreadData *>(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<ThreadData *> 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;
}
测试结果如下:
在进程退出中,我们会关心进程的退出码和退出信号。
而线程不用关心异常信号,因为一旦出现异常信号整个进程都会退出。