C++并发编程实战第2版笔记

17 篇文章 0 订阅

p19 某个线程只可以join()一次

只要调用了join(),隶属于该线程的任何存储空间即会因此清除,std::thread对象遂不再关联到已结束的线程。

事实上,它(std::thread)与任何线程均无关联。其中的意义是,对于某个给定的线程,join()仅能调用一次;只要std::thread对象曾经调用过join(),线程就不再可汇合(joinable),成员函数joinable()将返回false。

p22 只有当joinable()返回true时才能调用detach()

若没有与std::thread对象关联的任何线程,便不能凭空调用detach()
只有当t.joinable()返回true时才可调用t.detach()

P21 在std::thread对象析构前,必须明确是等待还是分离线程

std::thread对象析构前,必须明确,是等待线程完成(join)还是要与之分离(detach)。

如果等待std::thread对象销毁之际还没决定好,那std::thread的析构函数将会调用std::terminate()终止整个程序。

示例代码:

#include <thread>
#include <iostream>

void do_lengthy_work()
{
    std::cerr << __FUNCTION__;
};

int main(void)
{

    {
        std::thread t(do_lengthy_work);
        //t.join()
        //理应在这里join或detach
    }

    while (1)
    {
    }
}

这里我们既不join也不detach,那么就会导致std::terminate()被调用,导致程序终止。
在这里插入图片描述

P25 移动语义

若源对象是临时变量,移动就会自动发生。
若源对象是具名变量,则必须通过调用std::move()直接请求转移。

标准库中有一些类的归属权语义是 是只可移动但不可复制的,如std::threadstd::unique_ptr

P23 向线程传递参数

线程具有内部存储空间,参数会按照默认方式复制到该处,新创建的执行线程才能直接访问它们。

然后,这些副本被当成临时变量,以右值形式传给新线程上的函数或可调用对象。

——即使函数的相关参数应该是引用,上述过程依然会发生。

Example

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

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

本例中,向新线程传递的参数是buf,类型是char*,指向一个局部数组。
我们原本涉嫌,buf会在新线程内转换成std::string对象,但是,在此完成之前,oops()函数极有可能已经退出,buf所指的局部变量已经销毁。

这一问题的根源在于,bufstd::string的转换未及时发生,可以显式地转换为std::string传递:
std::thread t(f, 3, std::string(buf));

与之相反的是想要传递引用:

void func(Data& data);

int main()
{
	Data d;
	std::thread t(func, d);//这里会编译失败
}

解决办法是:std::thread t(func, std::ref(data))

P25 将类的成员函数设定为线程函数

class X{
public:
	void do_lengthy_work();
};

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

若要将某个类的成员函数设定为线程函数,应传入一个函数指针,指向该成员函数。

此外,还要给出合适的对象指针,作为该函数的第一个参数。

p41 std::mutex和类模板std::lock_guard<>

在C++中,通过构造std::mutex的实例来创建互斥,调用lock()成员函数对其加锁,调用unlock()解锁。

但不推荐直接条约成员函数的做法

因为,若按此处理,在函数以外的每条代码路径上都要调用unlock(),包括由于异常退出的路径。(注:这个路径应该指的是代码结束路径,如return、exit,总之,跳出的时候)

推荐用C++标准库提供的类模板std::lock_guard<>

示例代码:

#include <algorithm>
#include <list>
#include <mutex>

std::list<int> some_list;
std::mutex some_mutex;

void add_to_list(int new_value)
{
	std::lock_guard<std::mutex> guard(some_mutex);
	some_list.push_back(new_value);
}

void list_contains(int value_to_find)
{
	std::lock_guard<std::mutex> guard(some_mutex);
	if(std::find(some_list.begin(), some_list.end(), value_to_find) == some_list.end())
		return false;
	else
		return true;
}

如果使用的是C++17,还可以将std::lock_guard<>简化为std::lock_guard,省略模板参数列表。这是一个新特性,名为类模板参数推导

P75 条件变量std::condition_variable, std::condition_variable_any。

使用示例:

#include <condition_variable>
#include <mutex>
#include <queue>
#include <thread>


using data_chunk = int;

std::mutex mut;
std::queue<data_chunk> data_queue;
std::condition_variable data_cond;

bool more_data_to_prepare = true;

data_chunk
prepare_data ()
{
	return data_chunk();
}

void
data_preparation_thread ()
{
	while (more_data_to_prepare) {
		data_chunk data = prepare_data();
		{
			std::lock_guard<std::mutex> lk (mut);
			data_queue.push (data);
		}
		data_cond.notify_one();
	}
}

void
data_processing_thread ()
{
	while (true) {
		std::unique_lock<std::mutex> lk (mut);
		data_cond.wait (lk, [] { return !data_queue.empty(); });

		data_chunk data = data_queue.front();
		data_queue.pop();

		lk.unlock(); 

		// process data ...

		// next continue ...
	}
}

要注意的是:

  1. wait()

    • wait只有在条件为true(例如上面的lambda表达式)才返回,并保持互斥;
    • 否则,线程继续进入休眠,并解锁互斥。
    • wait()中线程可能苏醒并查验条件,若条件不满足则继续睡眠,这种叫做伪唤醒
    • C++标准规定,伪唤醒的数量和频率都不确定,因此,若判断函数有副作用,不建议选取它来查验条件。
  2. 调用notify_one()通知条件变量。

  3. 必须使用std::unique_lock,而不能使用std::lock_guard,因为需要中途解锁、重加锁,std::lock_guard不支持这些功能。

  • 在线程间传递数据的常见方法是使用队列。
  • 在必要范围之外锁住互斥是不当的设计思维。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

barbyQAQ

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

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

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

打赏作者

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

抵扣说明:

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

余额充值