首页 > 其他分享 >线程基础

线程基础

时间:2024-10-01 10:44:59浏览次数:1  
标签:std join thread 局部变量 基础 线程 func

线程发起

回调函数 func(a)
传递给回调函数的参数 a

std::thread t1(func, a)

线程等待

std::thread t1(func, a)

在其它线程中调用,其它线程等待t1线程执行完毕
t1.join()

仿函数作为参数时

class test
{
public:
	void operator()(std::string str){
		std::cout << "str is " << str << std::endl;
	}
};

错误调用
std::thread t2(test());
t2.join()

定义时没错,但是调用join时编译器会识别错误

错误原因
因为编译器会将t2当成一个函数对象, 返回一个std::thread类型的值, 函数的参数为一个函数指针,该函数指针返回值为background_task, 参数为void。可以理解为如下
"std::thread (*)(background_task (*)())"

修改方式
//可多加一层()
std::thread t2((background_task()));
t2.join();
//可使用{}方式初始化 C++11特性
std::thread t3{ background_task() };
t3.join();

Lambda表达式作为参数

std::thread t4([](std::string  str) {
    std::cout << "str is " << str << std::endl;
},  hellostr);
t4.join();

分离线程

线程允许采用分离的方式在后台独自运行,C++ concurrent programing书中称其为守护线程。

分离线程中可能存在的问题:局部变量提前释放

struct func {
    int& _i;
    func(int & i): _i(i){}
    void operator()() {
        for (int i = 0; i < 3; i++) {
            _i = i;
            std::cout << "_i is " << _i << std::endl;
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
    }
};
void oops() {
        int some_local_state = 0;
        func myfunc(some_local_state);
        std::thread functhread(myfunc);
        //隐患,访问局部变量,局部变量可能会随着}结束而回收或随着主线程退出而回收
        functhread.detach();    
}
// detach 注意事项
oops();
//防止主线程退出过快,需要停顿一下,让子线程跑起来detach
std::this_thread::sleep_for(std::chrono::seconds(1));

上面的例子存在隐患,因为some_local_state是局部变量, 当oops调用结束后局部变量some_local_state就可能被释放了,而线程还在detach后台运行,容易出现崩溃。
所以当我们在线程中使用局部变量的时候可以采取几个措施解决局部变量的问题

  • 通过智能指针传递参数,因为引用计数会随着赋值增加,可保证局部变量在使用期间不被释放,这也就是我们之前提到的伪闭包策略。
  • 将局部变量的值作为参数传递,这么做需要局部变量有拷贝复制的功能,而且拷贝耗费空间和效率。
  • 将线程运行的方式修改为join,这样能保证局部变量被释放前线程已经运行结束。但是这么做可能会影响运行逻辑。
设置为等待线程,functhread执行完后,函数才释放,局部变量再释放
通过调用 functhread.join() 方法,主线程将等待 functhread 线程完成其执行。在 functhread 线程完成执行并且 join() 返回之后,局部变量(如 some_local_state)才会被销毁。
void use_join() {
    int some_local_state = 0;
    func myfunc(some_local_state);
    std::thread functhread(myfunc);
    functhread.join();
}
// join 用法
use_join();

异常处理

主线程崩溃时,子线程也会异常退出,如果子线程处理的是一些重要信息或者任务,丢失这些信息很危险,所有采用异常捕捉,保证主线程崩溃时,子线程能够顺利执行完成再退出。

添加异常捕捉,主线程崩溃时,子线程也能顺利执行

void catch_exception() {
    int some_local_state = 0;
    func myfunc(some_local_state);
    std::thread  functhread{ myfunc };
    try {
        //本线程做一些事情,可能引发崩溃
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }catch (std::exception& e) {
        functhread.join();
        throw;
    }
    functhread.join();
}

但是用这种方式编码,会显得臃肿,可以采用RAII技术,保证线程对象析构的时候等待线程运行结束,回收资源。

线程守卫
class thread_guard {
private:
    std::thread& _t;
public:
    explicit thread_guard(std::thread& t):_t(t){}
    ~thread_guard() {
        //join只能调用一次
        if (_t.joinable()) {
            _t.join();
        }
    }
    thread_guard(thread_guard const&) = delete;
    thread_guard& operator=(thread_guard const&) = delete;
};


void auto_guard() {
    int some_local_state = 0;
    func my_func(some_local_state);
    std::thread  t(my_func);
    thread_guard g(t);
    //本线程做一些事情
    std::cout << "auto guard finished " << std::endl;
}
auto_guard();

慎用隐式转换

引用参数

thread原理

绑定类成员函数

class X
{
public:
    void do_lengthy_work() {
        std::cout << "do_lengthy_work " << std::endl;
    }
};
void bind_class_oops() {
    X my_x;
    std::thread t(&X::do_lengthy_work, &my_x);
    t.join();
}

注意:如果绑定的是普通函数,加不加&都可以,编译器会默认将普通函数名作为函数地址,但是若绑定的是类中成员函数,必须加&

void thead_work1(std::string str) {
    std::cout << "str is " << str << std::endl;
}
std::string hellostr = "hello world!";
//两种方式都正确
std::thread t1(thead_work1, hellostr);
std::thread t2(&thead_work1, hellostr);

使用move操作

有些资源为独占资源,不支持拷贝和赋值构造,所有通过move将资源所有权进行转移。

void deal_unique(std::unique_ptr<int> p) {
    std::cout << "unique ptr data is " << *p << std::endl;
    (*p)++;
    std::cout << "after unique ptr data is " << *p << std::endl;
}
void move_oops() {
    auto p = std::make_unique<int>(100);
    std::thread  t(deal_unique, std::move(p));
    t.join();
    //不能再使用p了,p已经被move废弃
   // std::cout << "after unique ptr data is " << *p << std::endl;
}

声明

此随笔内容是根据大佬博客进行学习。
原文链接:https://www.llfc.club/category?catid=225RaiVNI8pFDD5L4m807g7ZwmF

标签:std,join,thread,局部变量,基础,线程,func
From: https://www.cnblogs.com/xuejx/p/18442740

相关文章

  • 【多线程奇妙屋】 Java 的 Thread类必会小技巧,教你如何用多种方式快速创建线程,学并发
    本篇会加入个人的所谓鱼式疯言❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言而是理解过并总结出来通俗易懂的大白话,小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的.......
  • kali操作命令基础
    一:Linux目录结构在Linux中所有都是由文件的形式存在的/bin全名binaries二进制文件/boot启动的核心文件,如镜像/dev全名device存放外部设备如硬盘/etc全名etcetera配置文件和子目录/home用户主目录/root管理员主目录/run临时文件/sbin全名superuserbinaries超级......
  • 第一章 WebRTC基础框架介绍
    什么是WebRTCWebRTC(WebReal-TimeCommunication——网页实时通信)是一个基于浏览器的实时多媒体通信技术。该项技术旨在使Web浏览器具备实时通信能力;同时,通过将这些能力封装并以JavaScriptAPI的方式开放给Web应用开发人员,使得Web应用开发人员能够通过HTML标签和JavaScript......
  • [物理]运动学基础理论串讲
    运动学基础理论串讲公式·推论前言:运动学中,所有的公式都有其对应的几何意义。解决问题时,我们不应死套公式,应当在图像中解决问题。在图像中看清问题的本质。\(v_t=v_0+at\)。已知初速度和加速度求末速度。\(x=v_0t+\dfrac{1}{2}at^2\)。算位移的基础公式。\(v_t^2-v_0^2=2ax......
  • [操作系统]线程上下文切换
    单核处理器能够支持多线程执行代码就是因为线程的上下文切换。具体是如何做到的呢?CPU通过给每个线程分配CPU时间片来实现这个机制。什么是时间片?CPU分配给每个线程的时间。时间片非常短一般几十ms。CPU通过不停地切换线程执行,让我们感觉多个线程是同时执行的。什么是线程上下......
  • C++入门基础知识91(实例)——实例16【求两数最小公倍数】
    成长路上不孤单......
  • LInux基础——裸金属主机重装系统进入kernel Panic
    裸金属主机重装系统进入kernelPanic1、问题描述       租户裸金属主机重装系统,重启后进入”endKernelpanic–notsyncing:Fatalexception”; 2、问题分析  i.内存问题(非此类问题,忽略)     按照问题描述内核回退Calltrace是__pte_alloc内核物理内存调......
  • 多线程Day01
    多线程线程、进程、多线程程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念进程是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义,线程是CPU调度和执......
  • 初学线程
    线程概念线程是进程中的⼀个执行单元,负责当前进程中程序的执行,⼀个进程中至少有⼀个线程⼀个进程中是可以有多个线程多个线程共享同一个进程的资源,每个线程参与操作系统的统一调度进程相当于是由进程资源+主线程+子线程,组合而来程序由进程进行执行,进程由线程执行线程与进......
  • SqlSugar 基础知识
     1、实体特性[SugarColumn(IsPrimaryKey=true)]标识是否为主键[SugarColumn(IsIdentity=true)]是否为自增长[SugarColumn(ColumnName="id")]对应数据库表里面的某列[SugarColumn(IsIgnore=true)]忽略熟悉,在ORM会过滤掉[SugarColumn(ColumnDescription="创建......