《C++并发编程实战》读书笔记(1):线程管控

这篇具有很好参考价值的文章主要介绍了《C++并发编程实战》读书笔记(1):线程管控。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1、线程的基本管控

包含头文件<thread>后,通过构建std::thread对象启动线程,任何可调用类型都适用于std::thread

void do_some_work();

struct BackgroundTask
{
    void operator()() const;
};

//空的thread对象,不接管任何线程函数
std::thread t1;

//传入普通函数
std::thread t2(do_some_work);

//传入lambda函数
std::thread t3([]() { /*do something*/ });

//传入可调用对象
BackgroundTask task;
std::thread t4(task);
//不能使用std::thread t4(BackgroundTask()),虽然本意是传入临时变量,但这会被编译器解释成函数声明。多用一对圆括号或者使用列表初始化可以解决这个问题。
std::thread t5((BackgroundTask()));
std::thread t6{BackgroundTask()};

启动线程后,需要明确是等待它结束、还是任由它独自运行:

  • 调用成员函数join()会先等待线程结束,然后隶属于该线程的任何存储空间都会被清除,std::thread对象不再关联到已结束的线程。
  • 调用成员函数detach()会分离线程使其在后台运行,此后无法获得与它关联的std::thread对象。分离线程的归属权和控制权都转移给了C++运行时库,线程退出时与之关联的资源会被正确回收。

调用了join()或是detach()之后,其joinable()方法将返回false,所以也就不能再调用join()。不能对空的std::thread对象调用join()或是detach()。如果线程启动后既不调用join()也不调用detach(),那么当std::thread对象销毁时,其析构函数将调用std::terminate()终止整个程序。

2、向线程函数传递参数

若需向线程上的函数传递参数,直接向std::thread的构造函数添加更多参数即可。线程具有内部存储空间,参数会按照默认方式先复制到该处,然后这些副本被当作临时变量,以右值形式传递给线程上的函数。即使函数的相关参数按设想应该是引用,上述过程依然会发生。

void f(int i, const std::string& s);
std::thread t(f, 3, "hello");

上述代码在新线程上调用f(3, "hello"),尽管f()的第二个参数是std::string类型,但字符串内容仍然以指针const char*的形式传入,直到进入新线程的上下文环境后才转换为std::string类型。所以如果参数是指针,需要特别注意其生命周期,否则可能导致严重问题,例如:

void f(int i, const std::string& s);

void oops(int param)
{
    char buffer[1024];
    sprintf(buffer, "%d", param);
    std::thread t(f, 3, buffer);
    t.detach();
}

buffer是指向局部数组的指针,我们原本设想buffer会在新线程内转换成std::string对象,但在此完成之前,oops()函数很有可能已经退出,导致局部数组被销毁从而引发未定义的行为。这一问题的根源在于:std::thread的构造函数原样复制所提供的值,并未立即将其转换成预期的参数类型,等到转换发生时,指针可能已经失效。所以解决方法就是,在buffer传入std::thread的构造函数之前,就先把它转换成std::string对象:

std::thread t(f, 3, std::string(buffer));

除了指针外,传递引用也需要小心。例如我们想要的是非const引用:

void update_widget_data(WidgetData& data);

void oops()
{
    WidgetData data;
    std::thread t(update_widget_data, data);
    t.join();
}

根据update_widget_data函数的声明,参数需要以引用的方式传入,但std::thread的构造函数对此却毫不知情,它忽略了函数所期望的参数类型,直接复制了我们提供的值。然而,线程库的内部代码会把参数的副本(std::thread构造时由对象data复制得出,位于新线程的内部存储空间)以右值的形式传递给update_widget_data函数,所以这段代码会编译失败,因为不能向非const引用传递右值。解决方法就是使用std::ref()函数加以包装,这样传递给update_widget_data函数的就是指向data的引用,代码就能编译成功:

std::thread t(update_widget_data, std::ref(data));

要将某个类的成员函数设为线程函数,我们需要给出合适的对象指针作为第一个参数,成员函数的参数放在其后的位置。

class X
{
public:
    void do_lengthy_work();
}

X my_x;
std:thread t(&X::do_lengthy_work, &my_x);

对于只能移动、不能复制的对象,传递参数时需要使用std::move()来转移归属权。在下面的例子中,BigObject对象的归属权会发生转移,先进入新创建的线程的内部存储空间,再转移给process_big_object()函数。

void process_big_object(std::unique_ptr<BigObject>);

std::unique_ptr<BigObject> p(new BigObject);
std::thread t(process_big_object, std::move(p));

3、移交线程归属权

std::thread不能复制,但支持移动语义。对于一个具体的执行线程,其归属权可以在多个std::thread实例之间转移。

void some_function();

void some_other_function();

std::thread t1(some_function);
std::thread t2 = std::move(t1); //将线程的归属权显式地转移给t2
t1 = std::thread(some_other_function); //线程原本与std::thread临时对象关联,其归属权随即转移给t1
std::thread t3; //按默认方式构造,未关联任何线程
t3 = std::move(t2); //t2原本关联的线程的归属权转移给t3
//经过上面这些转移,t1与运行some_other_function的线程关联,t2没有关联线程,t3与运行some_function的线程关联

t1 = std::move(t3); //在这次转移之时,t1已经关联运行some_other_function的线程,因此std::thread的析构函数中会调用std::terminate(),终止整个程序。所以只要std::thread对象还在管控着一个线程,就不能简单地向它赋新值。

因为std::thread支持移动语义,所以只要容器同样知悉移动意图,就可以装载std::thread对象。因此我们可以写出下列代码,生成多个线程,然后等待它们运行完成。

void do_work(unsigned id);

void foo()
{
    std::vector<std::thread> threads;
    for (unsigned i = 0; i < 20; ++i)
    {
        threads.push_back(std::thread(do_work, i));
    }
    for (auto& entry : threads)
    {
        entry.join();
    }
}

4、识别线程

使用C++标准库的std::thread::hardware_concurrency()函数可以获取系统中逻辑处理器的数量。如果信息无法获取,该函数可能返回0。

线程ID的类型是std::thread::id,它有两种获取方法:

  • 在与线程关联的std::thread对象上调用成员函数get_id(),即可得到该线程的ID。如果std::thread对象没有关联任何执行线程,则调用get_id()返回的是按默认构造方式生成的std::thread::id对象,表示“线程不存在”。
  • 当前线程的ID可以通过调用std::this_thread::get_id()获取。

std::thread::id对象可以支持很多种操作:文章来源地址https://www.toymoban.com/news/detail-689474.html

  • 可随意进行复制操作或比较运算。
  • 可用作关联容器(std::setstd::mapstd::multisetstd::multimap)的键值,无序关联容器(std::unordered_setstd::unordered_mapstd::unordered_multisetstd::unordered_multimap)的键值,或用于排序。
  • 写到输出流,例如std::cout << std::this_thread::get_id();

到了这里,关于《C++并发编程实战》读书笔记(1):线程管控的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 并发编程: 2. 线程管控

    给定一个线程,只要令std::thread对象与之关联,就能管控该线程的几乎每个细节。 2.1.1 发起线程 线程通过构建std::thread对象而启动,该对象指明线程要运行的任务(函数)。简单的任务,函数结束返回,线程随即终止。复杂的任务情况下,函数可以由函数对象表示,还接受参

    2024年02月05日
    浏览(41)
  • c++并发编程实战-第3章 在线程间共享数据

    多线程之间共享数据,最大的问题便是数据竞争导致的异常问题。多个线程操作同一块资源,如果不做任何限制,那么一定会发生错误。例如: 输出: 显然,上面的输出结果存在问题。出现错误的原因可能是: 某一时刻, th1线程获得CPU时间片,将g_nResource从100增加至200后时

    2024年02月08日
    浏览(38)
  • java并发编程之美第五章读书笔记

    CopyOnWriteArrayList 线程安全的ArrayList,对其进行的修改操作都是在底层的一个复制的数组(快照)进行的,也就是写时复制策略 类图 每一个对象里面有一个array数组进行存放具体的元素,ReentrantLock独占锁对象用来保证同时只有一个线程对array进行修改,这里只要记得ReentrantLock是独占锁

    2024年02月03日
    浏览(45)
  • C++线程入门:轻松并发编程

            在现代计算机应用程序中,我们经常需要处理并发任务,这就需要使用多线程来实现。C++是一种功能强大的编程语言,提供了丰富的线程支持,使得并发编程变得相对容易。         C++ 线程是一种多线程编程模型,可以在同一个程序中同时执行多个独立的任务

    2024年02月04日
    浏览(41)
  • 《C++高级编程》读书笔记(七:内存管理)

    1、参考引用 C++高级编程(第4版,C++17标准)马克·葛瑞格尔 2、建议先看《21天学通C++》 这本书入门,笔记链接如下 21天学通C++读书笔记(文章链接汇总) 1. 使用动态内存 1.1 如何描绘内存 在本书中,内存单元表示为一个带有标签的框,该标签表示这个内存对应的变量名,方

    2024年02月08日
    浏览(87)
  • JUC并发编程学习笔记(十)线程池(重点)

    线程池:三大方法、七大参数、四种拒绝策略 池化技术 程序的运行,本质:占用系统的资源!优化资源的使用!- 池化技术(线程池、连接池、对象池......);创建和销毁十分消耗资源 池化技术:事先准备好一些资源,有人要用就拿,拿完用完还给我。 线程池的好处: 1、

    2024年02月06日
    浏览(46)
  • JUC并发编程学习笔记(一)认知进程和线程

    进程 一个程序,如QQ.exe,是程序的集合 一个进程往往可以包含多个线程,至少包含一个 java默认有两个线程,GC垃圾回收线程和Main线程 线程:一个进程中的各个功能 java无法真正的开启线程,因为java是运行在虚拟机上的,所以只能通过C++,通过native本地方法调用C++开启线程

    2024年02月06日
    浏览(55)
  • Java并发编程学习笔记(一)线程的入门与创建

    认识 程序由指令和数据组成,简单来说,进程可以视为程序的一个实例 大部分程序可以同时运行多个实例进程,例如记事本、画图、浏览器等 少部分程序只能同时运行一个实例进程,例如QQ音乐、网易云音乐等 一个进程可以分为多个线程,线程为最小调度单位,进程则是作

    2024年02月16日
    浏览(54)
  • c++并发编程实战-第4章 并发操作的同步

    想象一种情况:假设晚上坐车外出,如何才能确保不坐过站又能使自己最轻松? 这种方式存在双重浪费: 线程 th1(wait_for_flag)须不断查验标志,浪费原本有用的处理时间,这部分计算资源原本可以留给其他线程使用。 线程 th1(wait_for_flag)每次循环都需要给互斥上锁,导致

    2024年02月08日
    浏览(42)
  • 《Java并发编程实战》课程笔记(二)

    在单核时代,所有的线程都是在一颗 CPU 上执行,CPU 缓存与内存的数据一致性容易解决。 因为所有线程都是操作同一个 CPU 的缓存,一个线程对缓存的写,对另外一个线程来说一定是可见的。 一个线程对共享变量的修改,另外一个线程能够立刻看到,我们称为可见性。 多核

    2024年02月06日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包