文章目录
● 线程概念
这里 , 首先也要对进程的概念进行一次纠正和总结 , 然后引入线程的概念 .
-
什么是进程 ?
进程是承担分配系统资源的实体 . 概念有点抽象 , 以下的分配资源便可以理解了 . -
什么是线程 ?
线程是 CPU 调度的基本单位 .
以上的概念都是有点抽象的概念 , 具体啥意思呢 ?? 我们通过下面的资源分配具体看一看 !
● 深刻理解资源分配
这里先理解一下进程的概念 :
具体的资源分配 :
● 理解线程概念 (掌握)
什么是线程 ?? 不能光有概念吧 !
- 图示什么是进程 ?
- 那什么是线程 ?
- Linux 中真的存在线程吗 ??
总结 , 什么是线程和进程 ? (面试可直接用) (掌握)
● 进程和线程的区别是什么? (全面 , 适合面试)
● 线程控制 - 操作
以上对线程的概念有了深刻的理解 , 那么现在就要对线程的使用有进一步了解了 !
因为线程在Linux 中是轻量级进程模拟的 , 所以控制和进程也类似 , 创建 , 等待 …
一 、 POSIX 线程库
这是个什么玩意 ??
- 与线程有关的函数构成了⼀个完整的系列,绝大多数函数的名字都是以pthread_打头的
- 要使用这些函数库,要通过引入头文件 <pthread.h>
- 链接这些线程函数库时要使用编译器命令的 -lpthread选项
为什么要这样呢 ??
Linux 中本身是没有线程的概念的 , 都是进程的概念 , 线程的底层也是轻量级进程 , 但是为了实现线程的模式 , 所以操作系统的设计者就在用户层对轻量级进程进行了封装 , 将其封装为真正的线程 , 也是为了一直性 , 我们用户在上层使用时 , 只用知道线程就行了 , 而底层用轻量级进程 .
那为什么编译时要带 -lpthread 的选项呢 ??
线程其实是在 gblic 中给我们封装好了 , 将其打包成库 , 所以我们可以使用线程 , 然后我们的可执行程序和库其实都是 ELF 格式的 , 而 pthread 又是一个动态库 , 所以我们的程序在程序中使用线程是要依赖动态库 pthread 库的 , 所以需要动态链接该库 , 动态链接的方法就是 : -l + 库名 !
二 、 线程的创建
使用 :
#include <iostream>
#include <pthread.h>
#include <cstring>
#include <string>
#include <unistd.h>
//方法
void* routine(void* args)
{
std::string name = (char*)args;
//pthread_self() -- 这个函数是用来获取线程的 ID 的
pthread_t tid = pthread_self();
std::cout << "线程 : " << name << " , tid : " << tid << std::endl;
return nullptr;
}
int main()
{
//创建线程
// int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
// void *(*start_routine) (void *), void *arg);
//返回值 : 创建成功返回 0 , 否则错误码被设置
pthread_t tid; // 线程的id
//不关心属性
int ret = pthread_create(&tid , nullptr , routine , (void*)"thead-1");
if(ret == 0)
{
std::cout << "创建线程成功 !" << std::endl;
}
else
{
std::cout << strerror(ret) << std::endl;
}
//如果线程异常退出了 , 那么整个进程也就结束了 !!!!
while(true)
{
std::cout << "我是 main 主线程" << std::endl;
sleep(1);
}
return 0;
}
三 、 线程的终止
线程的终止有三种 :
- return 返回终止当前线程 (最常用的方式)
- 线程调用 pthread_exit() 终止自己
- 线程调用 pthread_cancel() 终止同一进程中的另一个线程
void* routine(void* args)
{
std::string name = (char*)args;
//pthread_self() -- 这个函数是用来获取线程的 ID 的
pthread_t tid = pthread_self();
int cnt = 5;
while(cnt--)
{
std::cout << "线程 : " << name << " , tid : " << tid << std::endl;
sleep(1);
}
//这里的return 返回了 , 就相当于终止了进人 routinue 中的当前执行流(线程)
return nullptr;
}
四 、线程的等待
因为在Linux 中线程就是轻量级进程 , 本质还是进程 , 所以如果线程终止了 , 不去等待该线程 , 就会造成内存泄漏的问题 !
void* ret= nullptr; // 是一个指针 , 需要开空间的
//线程退出了 , 需要等待
pthread_join(tid ,&ret); // 传二级指针意思就是 : 将返回的内容放到这个 ret 所指向的空间中 !
五 、多线程需要注意的重要点
多线程的创建和终止时需要注意 - >
这里先看一个现象 :
#include <iostream>
#include <string>
#include <pthread.h>
#include <vector>
//多线程中 , 除了主线程 , 其余线程均会进该执行流
void* routinue(void* args)
{
//显示类型转换
std::string name = static_cast<char*>(args);
std::cout << "我是新线程 : " << name << std::endl;
return nullptr;
}
int main()
{
std::vector<pthread_t> threads; // 所有线程管理起来 , 方便后面等待
//创建 5 个线程
for(int i = 1; i <= 5; ++i)
{
//这个里面存放每一个线程的 id 信息
char id[100]; //// error --------------- 不能这样写 !!!!
//格式化信息到 id 数组中
snprintf(id , sizeof(id) , "thread-%d" , i);
pthread_t tid;
//创建线程 , 不关注线程的属性 , 参数传的就是一个线程的 id 信息
int n = pthread_create(&tid , nullptr , routinue , id);
if(n == 0)
{
std::cout << "create thread success !" << std::endl;
threads.emplace_back(tid); // 将线程插入到 vector 中
}
}
std::cout << threads.size() << std::endl;
//多线程就需要一个一个等待了
for(int i = 0 ; i < threads.size(); ++i)
{
int n = pthread_join(threads[i] , nullptr);
if(n == 0)
{
std::cout << "join thread-" <<i<< "success !" << std::endl;
}
}
return 0;
}
以上的代码 , 我们主要要看的部分就是这里 :
//这个里面存放每一个线程的 id 信息
char id[100];
//格式化信息到 id 数组中
snprintf(id , sizeof(id) , "thread-%d" , i);
这里就存在问题了 , 运行代码看看效果 :
正确写法 :
char *id = new char[100];
// 格式化信息到 id 数组中
snprintf(id, 100, "thread-%d", i);
所以 , 总结一下 :
六 、线程分离
前面提到了 , 一个正常线程如果退出后 , 没有等待就会造成内存泄漏的问题 , 那么如果就不想等待线程呢 ?? 有没有这样的线程存在呢 ??? 是有的 , 这就要分离线程 !
1 . PS 命令查看线程
ps -aL | head -1
2 . 分离
主线程分离新线程 :
// 默认下 , 一个线程是可以被 joinable 即 : 可以被等待的
// 设置分离后 , 线程不用被等待 , 自己会释放
void *routinue(void *args)
{
std::string name = static_cast<const char *>(args); // 显示类型转换
std::cout << "新线程 : " << name << std::endl;
return nullptr;
}
int main()
{
pthread_t tid1 , tid2;
int cn = pthread_create(&tid1, nullptr, routinue, (void *)"thread-1");
if (cn == 0)
{
std::cout << "创建新线程成功 !" << std::endl;
}
// 设置线程分离 --- 分为两种 : 1 . 主线程分离新线程 2 . 新线程分离自己
// 1. 主线程分离新线程
int pn = pthread_detach(tid1);
if (pn == 0)
{
std::cout << "分离新线程成功 ![ " << tid1 << " ]"<< std::endl;
}
int cnt = 5;
while (cnt--)
{
std::cout << "我是 main(主) 线程 !" << std::endl;
}
//看看当分离后是否会 join -- 结果 , 不会 join , 所以 join 和 分离只有一个
int jn = pthread_join(tid1 , nullptr);
if(jn == 0)
{
std::cout << "等待主线程成功 !" << std::endl;
}
return 0;
}
新线程分离自己 :
这里介绍一个函数 , pthread_self() 获取自己的ID
void *routinue(void *args)
{
// 自己分离自己
int pn = pthread_detach(pthread_self());
if (pn == 0)
{
std::cout << "自己分离成功 ![ " << pthread_self() << " ]" << std::endl;
}
std::string name = static_cast<const char *>(args); // 显示类型转换
std::cout << "新线程 : " << name << std::endl;
return nullptr;
}
int main()
{
pthread_t tid1, tid2;
int cn = pthread_create(&tid1, nullptr, routinue, (void *)"thread-1");
if (cn == 0)
{
std::cout << "创建新线程成功 !" << std::endl;
}
// 看看当分离后是否会 join -- 结果 , 不会 join , 所以 join 和 分离只有一个
int jn = pthread_join(tid1, nullptr);
if (jn == 0)
{
std::cout << "等待主线程成功 !" << std::endl;
}
return 0;
}
● 线程ID和进程地址空间布局 (原理)
上面我们对线程的相关操作有了认识 , 但是还有以下问题 :
- 为什么 pthread_join() 传一个二级指针就能把需要的返回值带回来 ??
- 线程ID为什么那么大 ?? 到底是什么 ??
所以 , 我们讨论动态库 , 就可以讨论进程虚拟地址空间了 !
所以 , 可以理解创建线程和等待线程传 ID 了 , 通过ID标识和获取线程信息 !