[C++11 并发编程] 02 - join

1. 等待线程完成

若不等待线程完成,我们就需要确保该线程访问的数据都是有效的,直到该线程完成为止。比如如下代码,线程函数持有局部变量的指针或引用,当函数退出时,线程尚未执行完成。

#include <thread>
#include <iostream>

// 线程持有局部变量的指针
struct func
{
	int *i;
	func(int *i_) : i(i_){

	}

	void operator()()
	{
		for (unsigned j = 0; j < 100000; ++j)
		{
			*i = j; // 访问非法地址
		}
	}
};

// 不等待线程执行完成就退出
void oops()
{
	int some_local_state = 0;
	func my_func(&some_local_state);
	std::thread my_thread(my_func);
	my_thread.detach();
}

int _tmain(int argc, _TCHAR* argv[])
{
	oops();

	return 0;
}

要避免这种情况,需要使用std::thread实例的join()来替换my_thread.detach()的调用,这样就可以保证在函数退出前,线程已经结束。对一个给定的线程,只能调用一次join(),一旦调用了join(),此std::thread对象不再是可连接的,如果调用其的joinable()将返回false。

2. 在异常环境下的等待

我们需要在线程对象被销毁前调用join或detach方法,如果要detach,通常在线程启动后就立即调用detach方法。如果打算等待该线程,就需要仔细的选择在哪个位置调用join。如果在线程开始之后,调用join之前发生了异常,则可能跳过对join的调用。

为了避免应用程序在引发异常的时候被终止,你需要异常时也调用join。

void do_something_in_current_thread()
{
	throw("error");
}

// 不等待线程执行完成就退出
void oops()
{
	int some_local_state = 0;
	func my_func(&some_local_state);
	std::thread my_thread(my_func);

	try
	{
		do_something_in_current_thread();
	}
	catch (const char *err_msg)
	{
		my_thread.join();
		throw;
	}

	my_thread.join();
}

try/catch块可以确保无论函数时正常退出还是异常退出,都调用了线程的join方法,但是try/catch块看起来很啰嗦,也容易导致作用于混乱,更简单的办法是使用RAII-Resource Acquisition Is Initialization并提供一个类,在析构函数中调用join():

class thread_guard
{
	std::thread& t;
public:
	explicit thread_guard(std::thread& t_) :
		t(t_)
	{
	}

	// 析构函数中检查线程是否还未被join,若没有,则调用
	~thread_guard()
	{
		if (t.joinable())
		{
			t.join();
		}
	}

	// 将拷贝后赋值运算符标记为=delete以避免编译器自动生成,复制或赋值这样一个对象可能很危险,因为它可能比它要结合的线程的作用域存在得更久。
	thread_guard(thread_guard const&) = delete;
	thread_guard& operator=(thread_guard const&) = delete;
}

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-07 17:01:20

[C++11 并发编程] 02 - join的相关文章

python并发编程02/多进程

目录 python并发编程02/多进程 1.进程创建的两种方式 1.1开启进程的第一种方式 1.2开启进程的第二种方式 1.3简单应用 2.进程pid 2.1命令行获取所有的进程的pid tasklist 2.2代码级别如何获取一个进程的pid 2.3获取父进程(主进程)的pid 3.验证进程之间的空间隔离 4.进程对象join方法 5.进程对象其他属性 6.守护进程 python并发编程02/多进程 1.进程创建的两种方式 1.1开启进程的第一种方式 from multiProcessing

Java 并发编程 -- Fork/Join 框架

概述 Fork/Join 框架是 Java7 提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架.下图是网上流传的 Fork Join 的运行流程图,直接拿过来用了: 工作窃取算法 工作窃取(work-stealing)算法是指某个线程从其他队列里窃取任务来执行.那么为什么要使用这个算法呢?假如我们需要做一个比较大的任务,可以把这个任务分割为若干个互不依赖的子任务,为了减少线程间的竞争,把这些子任务分别放到不同的队列里,并为每个队列创

C++11 并发编程基础(一):并发、并行与C++多线程

正文 C++11标准在标准库中为多线程提供了组件,这意味着使用C++编写与平台无关的多线程程序成为可能,而C++程序的可移植性也得到了有力的保证.另外,并发编程可提高应用的性能,这对对性能锱铢必较的C++程序员来说是值得关注的. 回到顶部 1. 何为并发 并发指的是两个或多个独立的活动在同一时段内发生.生活中并发的例子并不少,例如在跑步的时候你可能同时在听音乐:在看电脑显示器的同时你的手指在敲击键盘.这时我们称我们大脑并发地处理这些事件,只不过我们大脑的处理是有次重点的:有时候你会更关注你呼吸的

C++并发编程 02 数据共享

在<C++并发编程实战>这本书中第3章主要将的是多线程之间的数据共享同步问题.在多线程之间需要进行数据同步的主要是条件竞争. 1  std::lock_guard<std::mutex> #include <list> #include <mutex> #include <algorithm> std::list<int> some_list; std::mutex some_mutex; void add_to_list(int n

使用 C++11 并发编程入门

一.认识并发和并行 先将两个概念, 并发与并行 并发:同一时间段内可以交替处理多个操作: 图中整个安检系统是一个并发设计的结构.两个安检队列队首的人竞争这一个安检窗口,两个队列可能约定交替着进行安检,也可能是大家同时竞争安检窗口(通信).后一种方式可能引起冲突:因为无法同时进行两个安检操作.在逻辑上看来,这个安检窗口是同时处理这两个队列 并行:同一时刻内同时处理多个操作: 图中整个安检系统是一个并行的系统.在这里,每个队列都有自己的安检窗口,两个队列中间没有竞争关系,队列中的某个排队者只需等待队

python基础-并发编程02

并发编程 子进程回收的两种方式 join()让主进程等待子进程结束,并回收子进程资源,主进程再结束并回收资源 from multiprocessing import Process import time def task(name): print(f'子进程{name}:starting--') time.sleep(1) print(f'子进程{name}:end--') if __name__ == '__main__': print('进入主进程--') pro_list = [] fo

Java并发编程(02):线程核心机制,基础概念扩展

本文源码:GitHub·点这里 || GitEE·点这里 一.线程基本机制 1.概念描述 并发编程的特点是:可以将程序划分为多个分离且独立运行的任务,通过线程来驱动这些独立的任务执行,从而提升整体的效率.下面提供一个基础的演示案例. 2.应用案例 场景:假设有一个容器集合,需要拿出容器中的每个元素,进行加工处理,一般情况下直接遍历就好,如果数据偏大,可以根据线程数量对集合切割,每个线程处理一部分数据,这样处理时间就会减少很多. public class ExtendThread01 { publ

C++11并发编程:原子操作atomic

一:概述 项目中经常用遇到多线程操作共享数据问题,常用的处理方式是对共享数据进行加锁,如果多线程操作共享变量也同样采用这种方式. 为什么要对共享变量加锁或使用原子操作?如两个线程操作同一变量过程中,一个线程执行过程中可能被内核临时挂起,这就是线程切换,当内核再次切换到该线程时,之前的数据可能已被修改,不能保证原子操作. C++11提供了个原子的类和方法atomic,保证了多线程对变量原子性操作,相比加锁机制mutex.locak(),mutex.unlocak(),性能有几倍的提升. 所需头文件

[C++11 并发编程] 01 - Hello World

C++11标准支持了并发,其中包含了线程管理,共享资源保护,线程间同步操作和底层原子操作等功能.我们先通过一个简单的示例看看C++11标准的多线程程序是什么样的. #include <iostream> #include <thread> // 引用用于管理线程的类的头文件 using namespace std; // 线程的入口函数,程序将在新创建的线程中打印log void hello() { cout << "Hello Concurrent Worl