线程概述
优点:
1.创建,调度,释放的量级比进程轻
2.可并发进行
3.计算为主的叫做计算应用,可分解为多线程实现
4.下载上传等操作为主的I/O密集应用,可以将文件分块进行并行处理
缺点
1.压缩,加密任务,对于单核运算的机器,单线程才是最好的,当启动多
线程时,线程的切换就会增多,效率反而降低了
2.缺乏访问控制,a线程对b线程有影响(同步和互斥就是解决这个问题)(但是a进程并不会对b进程有影响)
3.程序健壮性减低,任何一个线程报错,其他线程也会出现问题(但是进程不会,进程具有独立性)
4.多线程代码的编写需要更加注意代码,健壮性低的原因导致出现问题不易排查,书写需谨慎
应用场景:
1.I/O运行建议多线程,I/O阻塞多,多线程可以更好 的躲避这一个问题,提高用户体验
(忽略带宽等情况)
2.提高CPU密集型程序的执行效率
Linux进程VS线程:
线程除了共=共享进程地址空间之外,还会共享:
文件描述符表(struct_file files)
每个线程的每种信号处理机制都是共享的信号
工作目录
调度优先级
线程间虽然共享,但是也有独立的部分:
1.有独立的上下文,寄存器上下文
2. 每个线程都有自己独立的栈结构
进程与线程的创建模型
线程创建:
Linux中并不存在不存在线程的概念,只有轻量级进程,所以创建线程会通过pthread原生线程库进行修饰,
在用户看来是创建了一个线程,实际是只创建了一个轻量级进程
Linux就是这样设计的,维护性增加,且更符合软件工程
这个库是纯原生的,即不属于OS,也不属于语言层面的,有点像第三方库,所以在编译的时候要加上-l库名的操作
创建线程
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>
#include <functional>
#include <time.h>
#include <vector>
const int threadnum = 5;
using func_t = std::function<void()>; // functional的 函数模板,参数无,返回值为void,可接受这个类型的函数指针,函数lambda表达式等
class threadData
{
public:
threadData(const std::string &threadname, const uint64_t &ctime, func_t func)
: _threadname(threadname), _ctime(ctime), _func(func)
{
}
public:
std::string _threadname;
uint64_t _ctime;
func_t _func;
};
void Print()
{
std::cout << "我是线程执行的任务的一部分" << std::endl;
}
// 新线程
void *threadRountain(void *args)
{
threadData *td = static_cast<threadData *>(args); // 将args转化为threadData*的类型,更安全,编译时完成转化,将arg的性质传给threadData中的相符合的变量
while (true)
{
std::cout << "new thread " << "my name is: " << td->_threadname << ",creat time: " << td->_ctime << std::endl;
td->_func();
// if(td->_threadname=="thread-4")
// {
// int a = 10;
// a /= 0;
// }
sleep(1);
}
}
int main()
{
std::vector<pthread_t> pthreads;
for (int i = 0; i < threadnum; i++)
{
char threadname[64];
snprintf(threadname,sizeof(threadname),"%s-%lu","thread",i);
pthread_t tid;
threadData *td = new threadData(threadname, (uint64_t)time(nullptr), Print); // time获取时间戳
pthread_create(&tid, nullptr, threadRountain, td); // pthread_create函数的第四个参数可以传通用的指针类型,新创建的线程会执行threadRountain方法,将
pthreads.push_back(tid);
sleep(1);
}
for(const auto& e : pthreads)
{
std::cout << e << " ";
}
std::cout << std::endl;
while (true)
{
std::cout << "main thread " << std::endl;
sleep(3);
}
return 0;
}
一个线程出错,这个进程就会退出
这个线程id和ps -aL展示的id怎么不一样呢? 线程怎么获取自己的tid呢?
新线程作用域内:
主线程作用域内:
将这个数转为16进制看看发现本质是一个地址
线程终止
如何终止这个线程?
return
或者是 pthread_exit()
exit()会将当前线程和进程的都会退出,所以退出线程建议用pthread_exit()或者是return
这两种方法
获取线程的返回值:
return的值或者是pthread_exit()的值
线程等待
线程退出默认要进行等待:
1.线程退出没有等待会造成类似进程的僵尸问题(当然定义不是这样定义的,只是会造成这样类似的问题 )
2.线程的退出,主线程怎么获取新线程的返回值
pthread_join(pthread_t, void **); //这个等待是阻塞式等待
要等待的线程id 获得这个子线程退出码(输出型参数,要得到线程的退出信息,所以传一个指针,没毛病,因为原先就是一个指针,所以要传入二级指针)
等待成功返回0,等待错误返回错误码
同样,pthread_exit(void *);也能做到
用法为:
pthread_exit((void *)“thread-1 done”);
可以发现相比进程的异常,线程的异常处理就很简单,因为线程作为进程的一部分,线程异常意味着进程异常,那么线程连同进程会一同退出
线程的退出设为void*意味着可以通过自定义的方式得到我们想要的输出信息(定义一个类的对象来存储这个信息)
线程分离
创建的分离状态
线程的退出不做回收机制就是线程的分离状态,这种情况下是OS会自动进行回收处理,不再需要join
默认情况下线程是joinable的
线程怎么进行自己的分离?
pthread_detach()
将线程与主线程进行分离
写在子线程内
pthread_detach(pthread_self())
在主线程内
pthread_detach(pthread_t id);
在子线程内等待时,需要注意:
当线程启动时,子线程和主线程哪个开始执行是不可而知的,所以针对下面的代码
可能是当子线程还没有执行到pthread_detach()函数,pthread_join()就已经开始执行了
所以最好的验证方式是在启动子线程后,主线程等待1second
若不信,可验证子线程等待时间比主线程长,可以发现会先到pthread_join()函数,这时线程已经被等待了,分离无效
线程的生命周期相关的问题
线程取消(第三种退出线程的方式)
子线程会在执行cancel时立刻结束当前代码的执行,这个时刻之后的代码将不再执行
在主线程内:
pthread_cancel(pthread_t id)
子线程要5s才能执行完毕,主线程在第3s就取消
子线程未分离,中途被取消,join正常,cancel正常,线程返回值出错为-1,这个-1是一个PTHREAD_CANCELED宏,值为-1
线程被分离,该线程可以被取消,但是不能join,join会返回错误码,因为被OS自动回收了
以上都不是系统直接提供的接口,而是原生线程库pthread提供的接口
用户与OS之间如何利用pthread库来管理线程?
1.系统调用的问题
线程要有独立的属性:
a.上下文(OS将上下文以轻量级进程的形式存储在OS表中)
b.栈空间(每个新线程的栈在库中维护,库能对一段内存空间进行维护,比如这边的pthread库,file文件库的缓冲区)
fork()底层的实现
第一个参数是一个回调函数,表示新创建的进程用于什么工作
第二个参数则是栈,允许用户传入栈空间的地址
第三个参数用于标识是创建进程还是轻量级进程(线程)
2.线程库作为动态库是共享库,所以,内部要管理整个系统中多个用户启动的所有的线程
站在语言角度理解pthread库
通常情况下ll是ls -al的缩写,但是不同版本可能不同
使用alias ll查看
ll 被定义为 ls -alF,其中 -F 选项会在可执行文件名后面添加 *,在目录名后面添加 /,在符号链接后面添加 @ 等。
对于一个全局变量,是能被主线程和子线程共享的
对全局变量加上 __thread,就能将线程进行局部存储,即每个线程各自拥有一份
利用这个特性可以在线程内获取他自己的LWP
__thread相当于是给g++的编译选项,所以他修饰的变量必须是内置类型,如int等类型,不能是string等
同样,在线程内可以fork,也可以exec*程序替换,这个替换会将整个进程的所有内容换为新进程
喜欢不妨点个关注~~~