前一篇没用markdown编辑器感觉不好看,删了重新发

本篇主要讲述线程的管理,主要包括创建和使用线程

启动线程

线程出现是为了执行任务,线程创建时会给一个入口函数,当这个函数返回时,该线程就会退出,最常见的main()函数就是主线程的入口函数,在main()函数返回时主线程就结束了。

如何启动一个线程呢?就如上所述,需要给线程对象一个入口函数。

#include <iostream>
#include <thread>
using namespace std; void hello()
{
cout << "hello world";
} int main()
{
thread t(hello);
t.join();
return 0;
}

上述代码中实现了一个简单的线程

一旦线程被启动,就必须要显式地决定是要等待线程完成还是让线程自己运行,这个决定必须要在线程对象销毁前完成,否则,程序将会被终止(抛出异常),如果不等待线程完成,则需要保证线程访问的数据必须是有效的,直到该线程终止,在这种情况下,必须要小心的使用局部变量,局部变量的指针和引用。

PS:需要注意的是等待线程完成和分离线程必须要在线程对象销毁前完成,并不是在线程函数执行结束前完成。原因是线程对象被销毁后不能直接与线程通信,将无法等待或者分离。

例如:

#include <iostream>
#include <thread>
#include <stdlib.h>
#include <windows.h>
using namespace std; void hello()
{
cout << "hello world";
} int main()
{
thread t(hello);
Sleep(3000);
t.join();
return 0;
}

这个代码中线程执行的hello函数已经结束,依然可以join

等待线程完成

在上面的代码例子中,使用了线程对象的join函数,这个函数的作用就是等待线程完成。同时需要注意,一个线程不能被join两次。可以通过线程对象的joinable()函数判断当前函数是否可以被join。

std::thread t(do_thread_work);
if(t.joinable())
t.join();

分离线程

除了可以等待线程完成,还可以分离线程,让线程自己运行,也就是所谓的在后台运行线程,当线程对象被销毁后,线程可能仍在运行,此时,没有直接的方法可以与其通信,不能再通过线程对象获取到该线程,也不能再被join,所以要注意资源的请求和释放。

参照守护进程的概念,被分离的线程通常被称作守护线程。

std::thread t(do_some_background_work);
t.detach();
assert(!t.joinable());

为了分离线程,线程对象必须与一个线程相关联,不能在没有线程关联的线程对象上使用detach(),join()函数也是同理,因此也可以使用joinable()函数来判断当前函数是否可以detach。

在异常环境下的等待

如上所述,必须在线程对象被销毁之前调用join()或者detach(),如果需要分离线程,一般会在线程对象创建好后立刻调用,这样问题不大。但是如果打算等待线程完成,就需要考虑在哪个位置调用join。如果在线程开始之后join执行之前发生了异常,对join的调用可能就会被跳过。所以应该确保join函数被调用,以免程序被终止。

std::thread t(my_function);
try
{
do_something();
}
catch
{
t.join();
throw();
}
t.join();

当然还有其他的做法,利用RAII的思想,类似智能指针,对thread对象进行封装。

#include <iostream>
#include <thread> class thread_guard
{
public:
explicit thread_guard(std::thread& t)
:t_(t)
{}
~thread_guard()
{
if (t_.joinable())
{
t_.join();
}
}
private:
std::thread& t_;
}; void hello()
{
std::cout << "hello world";
} int main()
{
std::thread t(hello);
thread_guard g(t);
return 0;
}

传递参数给线程函数

线程的入口是个函数,作为使用者肯定是想传递一些函数参数的。操作也比较简单,将额外的参数传递给std::thread的够凹函数就可以了。但是,需要重视的一点是,参数默认上会被复制到内部存储空间,然后在那新创建的线程可以访问这些参数,即便函数中的相应参数期待着引用

这个意思就是说,传递参数给县城函数就像是给普通函数传递参数一样,默认传递的是形参,而且就算你加了引用,传递的也是形参。是std::thread的构造函数做的这个事情,他无视函数所期望的引用盲目的复制了所提供的值

解决方案也很简单,使用std::ref()包裹你想要以引用传递的参数。

void update_data_for_widget(widget_id w, widget_data& data);

void fun(widget_id w)
{
widget_data data;
std::thread t(update_data_for_widget, w, std::ref(data));
}

如果不这么做,传递的data在线程函数中的改变是不会起作用的。

转移线程的所有权

和std::unique_ptr类似,std::thread是可移动并且非可复制的,这意味着一般不会有两个线程对象指向同一个线程实例,如果出现此情况会发生异常。如果同时给一个线程对象关联两个线程实例,也会触发异常。

void some_function();
void some_other_function();
std::thread t1(some_function);
std::thread t2 = std::move(t1);
t1 = std::thread(some_other_function);
t3 = std::move(t2);
t1 = std::move(t3); //此项操作将会终止程序!

标识线程

线程标识符是std::thread::id类型的,获取方式有以下两种

  • 可以通过与线程相关联的std::thread对象调用get_id()方法获取。如果该线程对象没有相关联的线程,则此方法会返回一个默认构造的std::thread::id对象用来表示“没有线程”
  • 另外可以通过std::this_thread::get_id()获取当前现成的id。

    id可以自由地被用于复制和比较,或者是被排序。

【C++并发实战】(二)线程管理的更多相关文章

  1. day31_8.12并发编程二线程

    一.进程间的通信 在进程中,可以通过Queue队列进行通信,队列有两个特点: 1.先进先出.(先来的数据先被取出) 2.管道式存取.(数据取出一次后就不会在有了) 在python中有以下方法来操作数据 ...

  2. java并发系列(二)-----线程之间的协作(wait、notify、join、CountDownLatch、CyclicBarrier)

    在java中,线程之间的切换是由操作系统说了算的,操作系统会给每个线程分配一个时间片,在时间片到期之后,线程让出cpu资源,由其他线程一起抢夺,那么如果开发想自己去在一定程度上(因为没办法100%控制 ...

  3. Java并发(二十):线程本地变量ThreadLocal

    ThreadLocal是一个本地线程副本变量工具类. 主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不同的 ...

  4. Scala 深入浅出实战经典 第68讲:Scala并发编程原生线程Actor、Cass Class下的消息传递和偏函数实战解析

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-87讲)完整视频.PPT.代码下载: 百度云盘:http://pan.baidu.com/s/1c0noOt ...

  5. Android线程管理(二)——ActivityThread

    线程通信.ActivityThread及Thread类是理解Android线程管理的关键. 线程,作为CPU调度资源的基本单位,在Android等针对嵌入式设备的操作系统中,有着非常重要和基础的作用. ...

  6. android 进程/线程管理(二)----关于线程的迷思

    一:进程和线程的由来 进程是计算机科技发展的过程的产物. 最早计算机发明出来,是为了解决数学计算而发明的.每解决一个问题,就要打纸带,也就是打点. 后来人们发现可以批量的设置命令,由计算机读取这些命令 ...

  7. Java并发(二十一):线程池实现原理

    一.总览 线程池类ThreadPoolExecutor的相关类需要先了解: (图片来自:https://javadoop.com/post/java-thread-pool#%E6%80%BB%E8% ...

  8. 并发和多线程(二)--线程安全、synchronized、CAS简介

    线程安全性: 当多个线程访问一个类的时候,这个类始终表示出正确的行为,那么这个类是线程安全的. 无状态的对象一定是线程安全的,例如大部分service.dao.Servlet都是无状态的. 线程安全体 ...

  9. ios 多线程开发(二)线程管理

    线程管理 iOS和OS X中每一个进程(或程序)由一个或多个线程组成.程序由一个运行main方法的线程开始,中间可以产生其他线程来执行一些指定的功能. 当程序产生一个新线程后,这个线程在程序进程空间内 ...

  10. Java并发编程:线程池的使用

    Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...

随机推荐

  1. Linux下安装pip(遇到了python2.6升级为python2.7道路上的坑,原因已经找到,只差临门一脚了,以后补上)

    1.先说一下什么是pippip 是“A tool for installing and managing Python packages.”,也就是说pip是python的软件安装工具2.下面介绍怎么 ...

  2. mxonline实战3,编写首页及用户登录页面1

          对应github地址:首页和用户登陆1     一. 显示首页   1. 修改mxonline/setttings.py 在TEMPLATES代码块修改DIRS为 'DIRS': [os. ...

  3. javascript简要笔记

      零. 数据   0. 变量 分为字符串,数字,undefined, null,对象 undefined类型是只声明了变量,但是没赋值 可以使用typeof()函数来查看变量类型   例子1 var ...

  4. Es6 类class的关键 super、static、constructor、new.target

    ES6引入了Class(类)这个概念,作为对象的模板,通过class关键字,可以定义类.基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对 ...

  5. 架构师养成记--16.disruptor并发框架中RingBuffer的使用

    很多时候我们只需要消息中间件这样的功能,那么直需要RinBuffer就可以了. 入口: import java.util.concurrent.Callable; import java.util.c ...

  6. 2016级算法期末模拟练习赛-E.AlvinZH的青春记忆III

    1083 AlvinZH的青春记忆III 思路 难题,二分图. 说这是一个考察二分图的题目,你可以会说"不可能",这哪里像一个二分图了!这真的是一个二分图,考察的是最小顶点覆盖. ...

  7. eclipse首次使用的基本设置

    最近,一些刚开始学习Java的朋友使用eclipse遇到了一些编码导致的问题向我询问,那就总结一下首次安装eclipse后我们大体应该设置哪些基本东西吧~大神们呐就不用看啦. 一.修改工作空间默认编码 ...

  8. C++类的构造函数及定义

    定义一个普通的类时,一定要定义它自己的构造函数.原因有三:第一个原因是编译器只有在发现类不包含任何构造函数的情况下才会替我们生成一个默认的构造函数,一旦我们定义了一些其他的构造函数,那么除非我们再定义 ...

  9. 20190417 CentOS 7下安装ActiveMQ

    前言 用VMware安装CentOS 7.6,创建一个新的用户lihailin9073,并使用这个用户登陆CentOS系统 安装和启动 登陆ActivieMQ官网http://activemq.apa ...

  10. centos 6.6 设备 xxx 似乎不存在, 初始化操作将被延迟

    2019-04-02 问题描述: centos 6.6开启虚拟机之后,网卡服务不能正常启动,报错信息为:设备 xxx(网卡名称)似乎不存在, 初始化操作将被延迟 解决办法: 清空70-persiste ...