C++并发编程---线程的管理(《C++ Concurrency in Action》读书笔记)

《C++并发编程》(C++ Concurrency in Action)读书笔记
这本书中用到很多的高级用法,如RAII等手法,因此,看这本书之前建议先阅读《Effective c++》

1.线程的启动、等待、分离。

启动

C++中的线程的启动方式和Linux中的启动方式相似,但是启动参数减少了很多。仅需传入线程初始化函数(可调用对象),以及相应的参数即可。

class background_task
 {
 public: 
 void operator()() const {
  do_something(); do_something_else(); }
  };
  background_task f; 
  std::thread my_thread(f);

注意使用临时对象是,编译器将认为这是声名一个函数,而非开启一个新的线程。如下:

std::thread my_thread(background_task());

使用以下的方式可以避免这样的问题:

std::thread my_thread((background_task()));
std::thread my_thread{background_task()};
//Lamda表达式
std::thread my_thread([]{ 
	do_something(); 
	do_something_else(); 
});

有参数的函数:

void fun(int &a)
{
    cout << a << endl;
}



//创建线程对象
thread t1(fun, ref(a));//fun 中的参数为引用,则在传入参数时需用ref函数,否则将会报错
t1.join();//等待主线程回收

成员函数

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

线程的等待回收

等待线程结束后对子线程进行资源回收(join函数)。但是必须注意异常的发生,当程序发生异常时,如果join函数未被执行,则这种情况将会导致join被跳过。所以我们会封装一个类,再该类的析构函数中调用join函数,这样即使发生异常,程序也会执行析构函数。

class thread_guard 
{ 
	std::thread& t; 
	public: 
	explicit thread_guard(std::thread& t_): t(t_) 
	{}
	~thread_guard() 
	{ 
		if(t.joinable()) // 同一个线程只能进行依次join
		{ 
			t.join(); // 
		} 
	}
	//禁止拷贝
	thread_guard(thread_guard const&)=delete; // 3 
	thread_guard& operator=(thread_guard const&)=delete; 
};

struct func;
void f() 
{ 
	int some_local_state=0; 
	func my_func(some_local_state); 
	std::thread t(my_func); 
	thread_guard g(t); 
	do_something_in_current_thread(); 
}   
//当线程执行到④处时,局部对象就要被逆序销毁了。
//因此,thread_guard对象g是第一个被销 毁的,
//这时线程在析构函数中被加入②到原始//线程中。
//即使do_something_in_current_thread 抛出一个异常,这个销毁依旧会发生。

线程的分离

分离的线程将会在后台运行,因此,分离的线程将不能调用join函数,被分离的线程在在结束后由系统保证他被回收。

分离线程需要格外注意不能引用会被提前销毁的对象,如下:

struct func 
{ 
	int& i; 
	func(int& i_) : i(i_) {} 
	void operator() () 
	{ 
		for (unsigned j=0 ; j<1000000 ; ++j) 
		{ 
			do_something(i); // 1. 潜在访问隐患:悬空引用 
		} 
	} 
};
void oops() 
{ 
	int some_local_state=0; 
	func my_func(some_local_state); 
	std::thread my_thread(my_func); 
	my_thread.detach(); // 2. 不等待线程结束 
}

这个例子中,已经决定不等待线程结束(使用了detach()②),所以当oops()函数执行完成时 ③,新线程中的函数可能还在运行。如果线程还在运行,它就会去调用do_something(i)函数 ①,这时就会访问已经销毁的变量。如同一个单线程程序——允许在函数完成后继续持有局部 变量的指针或引用;当然,这从来就不是一个好主意——这种情况发生时,错误并不明显, 会使多线程更容易出错。

2.所有权转移

C++标准库中有很多资源占有(resource-owning)类型, 比如 std::ifstream , std::unique_ptr 还有 std::thread 都是可移动,但不可拷贝。这就是说线程可以转移所有权,按时不能进行复制。

void some_function(); 
void some_other_function(); 
std::thread t1(some_function); // 1 
std::thread t2=std::move(t1); // 2 
t1=std::thread(some_other_function); // 3 为什么不显式调用 std::move() 转 移所有权呢?
										//因为,所有者是一个临时对象——移动操作将会隐式的调用。
std::thread t3; // 4 
t3=std::move(t2); // 5 
t1=std::move(t3); // 6 赋值操作将使程序崩溃 ,t1已经有了一个关联 的线程(执行some_other_function的线程),
				  //所以这里系统直接调用 std::terminate() 终止程 序继续运行。

当所有权可以在函数内部传递,就允许 std::thread 实例可作为参数进行传递,代码如下:

void f(std::thread t); 
void g() 
{ 
	void some_function(); 
	f(std::thread(some_function)); 
	std::thread t(some_function); 
	f(std::move(t)); 
}

std::thread 支持移动的好处是可以创建thread_guard类的实例,并且拥有 其线程的所有权。

移动语义的用法:

class scoped_thread 
{ 
	std::thread t; 
	public: 
	explicit scoped_thread(std::thread t_): // 1 
	t(std::move(t_)) 
	{ 
		if(!t.joinable()) // 2 
		throw std::logic_error(“No thread”); 
	}
	~scoped_thread() 
	{ 
		t.join(); // 3 
	}
	scoped_thread(scoped_thread const&)=delete; 
	scoped_thread& operator=(scoped_thread const&)=delete; 
};


struct func; 
void f() 
{ 
int some_local_state; 
scoped_thread t(std::thread(func(some_local_state))); // 4 
do_something_in_current_thread(); 
}

创建线程组:

void do_work(unsigned id); 
void f() 
{ 
	std::vector<std::thread> threads; 
	for(unsigned i=0; i < 20; ++i) 
	{ 
		threads.push_back(std::thread(do_work,i)); // 产生线程, 临时对象,将会调用移动语义
	}
	std::for_each(threads.begin(),threads.end(), 
	std::mem_fn(&std::thread::join)); // 对每个线程调用join() 
} 

3.线程标识

第一种,可以通过调 用 std::thread 对象的成员函数 get_id() 来直接获取。如果 std::thread 对象没有与任何执 行线程相关联, get_id() 将返回 std::thread::type 默认构造值,这个值表示“无线程”。第二 种,当前线程中调用 std::this_thread::get_id() (这个函数定义在 头文件中)也可以 获得线程标识。

当用线程来分割一项工 作,主线程可能要做一些与其他线程不同的工作。这种情况下,启动其他线程 前,它可以将自己的线程ID通过 std::this_thread::get_id() 得到,并进行存储。就是算法核 心部分(所有线程都一样的),每个线程都要检查一下,其拥有的线程ID是否与初始线程的ID相 同。

std::thread::id master_thread; 
void some_core_part_of_algorithm() 
{ 
	if(std::this_thread::get_id()==master_thread) 
	{ 
		do_master_thread_work(); 
	}
	do_common_work(); 
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值