一篇blog带你深入了解线程操作

19 篇文章 0 订阅

线程概述

优点:

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*程序替换,这个替换会将整个进程的所有内容换为新进程

喜欢不妨点个关注~~~

  • 15
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

温有情

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值