🤖个人主页:晚风相伴-CSDN博客
💖如果觉得内容对你有帮助的话,还请给博主一键三连(点赞💜、收藏🧡、关注💚)吧
🙏如果内容有误或者有写的不好的地方的话,还望指出,谢谢!!!
让我们共同进步
下一篇《线程的互斥和同步》敬请期待
目录
🔥Linux线程的概念
👍理解Linux下的线程
线程的异常
🔥进程VS线程
线程控制
🔥线程相关函数
线程创建
线程等待
线程终止
获取线程ID
线程分离
🔥Linux线程的概念
比较官方的答案:线程是进程中的一条执行流,它是被系统独立调度和分配的基本单位。在一个进程内的多个线程可以共享该进程所拥有的全部资源,并且这些线程可以并发执行。简而言之,线程是程序中一个单一的顺序执行流,允许在单个程序中同时运行多个线程以完成不同的工作,这种技术被称为多线程。
举个例子:一个工厂里面有很多的车间,每个车间里都有许多的工人,每个工人各司其职,完成领导交代的任务。因此这里的工人就可以想象成是线程,每个车间就可以想象成是一个进程,而工厂就可以想象成是一台计算机。每一台计算机内可以拥有许多的进程,而在进程内部又可以有许多条执行流,可以分别处理不同的任务。
👍理解Linux下的线程
我们知道进程具有独立性,每一个进程都有自己独立的PCB(task_struct进程控制块)、地址空间、页表等。
现在有这样的一个技术,我们可以在进程内部创建多个task_struct,并且这些task_struct共享该进程的地址空间、页表等。而这些一个个的task_struct就是线程(执行流)。
在Linux内核中只给进程设计了专门的数据结构,而没有给线程设计专门的数据结构,因为Linux的大佬认为进程和线程在很多处理上都是一样的,没必要再给线程设计专门的数据结构,所以Linux中的线程结构很多都是复用的进程的。因此Linux系统下没有真正意义上的线程结构,而是用进程的机制模拟实现的线程。而Windows系统中有线程相关的数据结构,所以Windows的结构更复杂
综上对于线程更准确的定义是:线程是一个进程内部的控制序列(task_struct)。
线程在进程内部运行,本质是在进程地址空间内运行。
线程的异常
- 单个线程如果出现除零、野指针问题导致崩溃,进程也会随着崩溃
- 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随之退出。
- 如果在线程中调用exec系列函数或者exit函数也会导致给进程退出。
可以理解为:进程和线程是一体的,所以一荣俱荣,一损俱损
🔥进程VS线程
进程是资源分配的基本单位,而线程是调度的基本单位。
线程虽然共享进程的数据,但需要有自己的一部分数据:线程ID、寄存器、栈、errno、信号屏蔽字、调度优先级等。
进程的多个线程共享 同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
- 文件描述符表
- 每种信号的处理方式
- 当前的工作目录
- 用户ID和组ID
有了线程的概念之后,在看待之前的进程时,就可以理解为内部只有一条执行流的进程。
在Linux系统中,CPU是不区分进程和线程的,因为线程也是用进程的机制来模拟实现其功能的,但在CPU看来具有多条执行流的进程比单执行流的进程更加的轻量化,原因如下:
- 在CPU内部是有缓存的,当执行程序时,是需要先将内存中的代码和数据预读进缓存的。
- 当一个进程的时间片到了需要被切走时,要把进程对应的上下文数据全部保存起来再切走,并且地址空间、页表等也都需要被切走,而此时的缓存也立即失效了,当下一个进程来了,就需要重新将自身的代码和数据预读进缓存。
- 而当一个线程的时间片到了需要被切走时,只需要将自己的上下文数据保存起来切走就行了,也不需要重新将代码和数据预读进缓存。
所以Linux下的线程也会被称为轻量级进程。
进程和线程的关系如下图
线程控制
因为Linux中是用进程机制模拟实现的线程的,所以Linux并不能直接给我们提供线程相关的接口,只能提供轻量级进程的接口,但在用户层给我们实现了一套多线程的方案,并且以库的方式提供给用户进行使用——pthread线程库(原生线程库)
关于线程库的一些了解
- 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthrad_”打头的
- 要使用这些函数库,就需要引入头文件<pthread.h>
- 链接这些线程函数库是要使用编译器命令的“-lpthread”选项
🔥线程相关函数
线程创建
参数
- thread:返回线程ID
- attr:设置线程的属性,attr为nullptr表示使用默认属性
- start_routine:是一个函数地址,线程启动后要执行的函数
- arg:传给线程启动函数的参数
返回值:成功返回0,失败返回错误码
示例代码
#include <iostream>
#include <pthread.h>
#include <string>
#include <cstdio>
#include <unistd.h>
using namespace std;void *threadRun(void *args)
{const string name = (char *)args;while (true){cout << name << ", pid: " << getpid() << endl;sleep(1);}
}int main()
{pthread_t tid[5];char name[64];for (int i = 0; i < 5; i++){snprintf(name, sizeof name, "%s-%d", "thread", i + 1);pthread_create(tid + i, nullptr, threadRun, (void *)name);sleep(1);//缓解传参的bug问题}while (true){cout << "main thread, pid: " << getpid() << endl;sleep(3);}return 0;
}
结果演示
其中ps -aL命令可以用来查看线程
LWP就是轻量级进程的意思
从结果就可以看出,每次线程的顺序都是不一样的,这是因为调度器调度的问题。
一旦一个线程出现了异常,那么整个进程就会退出。
示例代码
void *threadRoutine(void *args)
{int i = 0;while (true){cout << "这是新线程: " << (char *)args << " running..." << endl;sleep(1);int a = 10;a /= 0;//除0错误}cout << "新线程退出..." << endl;return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");return 0;
}
结果演示
线程等待
没错线程也是需要等待的,因为如果主线程不等待,即会引起类似于僵尸问题,导致内存泄漏。
参数:
- thread:线程ID
- retval:指向一个指针,该指针指向线程退出的返回值
返回值:成功返回0,失败返回错误码
示例代码
#include <iostream>
#include <pthread.h>
#include <string>
#include <cstdio>
#include <unistd.h>
using namespace std;void *threadRoutine(void *args)
{int i = 0;while(true){cout << "这是新线程: " << (char*)args << " running..." << endl;sleep(1);if(i++ == 10) break;}cout << "新线程退出..." << endl;return (void*)10;//线程退出的返回值
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");void* ret = nullptr; pthread_join(tid, &ret);cout << "main thread wait done ... mian quit ... new thread quit: " << (long long)ret << endl;return 0;
}
结果演示
不只是可以返回一个数字,返回一组数也是可以的。
示例代码
void *threadRoutine(void *args)
{int i = 0;int *data = new int[10];while (true){cout << "这是新线程: " << (char *)args << " running..." << endl;sleep(1);data[i] = i;if (i++ == 10)break;}cout << "新线程退出..." << endl;return (void *)data; // 线程退出的返回值
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");int *ret = nullptr;pthread_join(tid, (void**)&ret);cout << "main thread wait done ... mian quit ... new thread quit:" << endl;for (int i = 0; i < 10; i++){cout << ret[i] << " ";}cout << endl;return 0;
}
结果演示
线程终止
如果需要终止某个线程而其它线程不受影响,可以有三种方法:
- return,这种方法对主线程不适用,在main函数中return相当于调用exit。
- 调用pthread_exit终止自己
- 在一个线程中调用pthread_cancel终止同一个进程中的另一个线程。
参数:
- retval:和线程等待那的参数一样
返回值:无返回值
参数:
- thread:线程ID
返回值:成功返回0,失败返回错误码
调用pthread_cancel终止的话,那么pthread_join等待接收到的就是常数PTHREAD_CANCELED。
示例代码
void *threadRoutine(void *args)
{int i = 0;while (true){cout << "这是新线程: " << (char *)args << " running..." << endl;sleep(1);if (i++ == 5)break;}cout << "新线程退出..." << endl;pthread_exit((void*)10);
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");void *ret = nullptr;pthread_join(tid, &ret);cout << "main thread wait done ... mian quit ... new thread quit:" << (long long)ret << endl;return 0;
}
结果演示
获取线程ID
返回值:线程ID
pthread_create函数会产生一个线程ID,存放在第一个参数指向的地址中,但是这个线程ID和ps -aL命令查看的ID是不一样的,用ps -aL查看的ID是属于进程调度范畴的,因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一标识该线程。而pthread_create参数中的线程ID是指向一个虚拟内存单元(这个单元是在地址空间中的共享区),该内存单元的地址即为新创建线程的线程ID。
Linux线程库中提供了pthread_self函数用来获取线程自身的ID,其本质是一个地址。
如图所示:
示例代码
void *threadRoutine(void *args)
{cout << "pthread_self:" << pthread_self() << endl;cout << "这是新线程: " << (char *)args << " running..." << endl;pthread_exit((void*)10);
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");cout << "tid:" << tid << endl;void *ret = nullptr;pthread_join(tid, &ret);cout << "main thread wait done ... mian quit ... new thread quit:" << (long long)ret << endl;return 0;
}
结果演示
线程的局部存储
每个线程都可以拥有自己的数据,使用__thread修饰全局变量就可以让每个线程中都各自拥有一个全局变量,这就实现了线程的局部存储。
示例代码
__thread int g_val = 10; //__thread修饰全局变量:线程的局部存储,让每一个线程各自拥有一个全局变量void *threadRoutine(void *args)
{while (true){cout << (char *)args << " : " << g_val << " 地址: " << &g_val << endl;g_val++;sleep(1);break;}return nullptr;
}int main()
{pthread_t tid; // 本质上是一个地址,各自线程的独立属性(独立的栈、id等)pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");while (true){cout << "main thread: " << g_val << " 地址: " << &g_val << endl;sleep(1);break;}pthread_join(tid, nullptr);return 0;
}
结果演示
线程分离
在一些场景下,如果我们不关心该线程的退出结果,那么我们对线程进程pthread_join就是一种负担,所以就可以使用线程分离函数pthread_detach,告诉操作系统,让这个线程退出时自动释放资源。
参数:
- thread:线程ID
返回值:成功返回0,失败返回错误码
可以是线程组内其它线程对目标线程进行分离,也可以是线程自己分离。
示例代码
void *threadRoutine(void *args)
{pthread_detach(pthread_self()); // 线程分离cout << "新线程退出了,并且自己释放了资源" << endl;return nullptr;
}int main()
{pthread_t tid; pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");while (true){sleep(1);break;}int n = pthread_join(tid, nullptr);cout << "n: " << n << " strerr: " << strerror(n) << endl;//因为线程自己释放了资源,所以pthread_join会失败return 0;
}
结果演示