C++
并发编程是指在C++
程序中使用多线程和同步机制来实现并发执行的功能。并发编程可以提高程序的性能和响应速度,同时也可以简化程序的设计和实现。
在C++
中,可以使用多个线程来并发执行不同的任务。为了避免竞态条件和数据不一致等问题,需要使用同步机制来协调多个线程之间的操作。常用的同步机制包括互斥锁、条件变量、原子操作等。
hello world开始
void hello () { cout << "hello world" << endl; } int main () { thread t (hello) ; t.join (); return 0 ; }
Basic
悬空引用
当 i 的生命周期结束时,func 对象中的引用 i 就变成了悬空引用,其指向的内存可能被其他程序占用或被释放,从而导致不可预测的错误。
void do_something (int &i) { cout << "do_something" << endl; } struct func { int &i; func (int &i_) : i (i_) {} void operator () () { for (unsigned j = 0 ; j < 1000000 ; ++j) { do_something (i); } } };
简洁机制
保线程在函数结束之前安全退出
void f () { int some_local_state = 0 ; func my_func (some_local_state) ; std::thread t (my_func) ; try { } catch (...) { t.join (); throw ; } t.join (); }
RAII
一种方式是使用“资源获取即初始化方式 (RAII,Resource Acquisition Is Initialization) ,并且提供一个类,在析构函数中使用join(),
class thread_guard { std::thread &t; public : explicit thread_guard (std::thread &t_) : t(t_) { } ~thread_guard () { if (t.joinable ()) { t.join (); } } thread_guard (thread_guard const &) = delete ; thread_guard &operator =(thread_guard const &) = delete ; }; void f1 () { int some_local_state=0 ; func my_func (some_local_state) ; std::thread t (my_func) ; thread_guard g (t) ; }
由于线程的管理得到了线程保护对象的管理,线程在程序结束时一定会被正确的处理,并且不会产生悬空的线程引用,保证了线程的安全退出.
Most Vexing Parse
Most Vexing Parse :声明的对象被解析为一个函数声明而不是对象定义。
class background_task {public : void operator () () const { cout << "ok" << endl; } }; thread my_thread1 (background_task())
解决如下:
使用多组括号:这种方法可以告诉编译器我们正在声明一个对象,而不是一个函数。
使用新的初始化语法:C11引入了C 的新初始化语法,即使用花括号{}来初始化对象。
thread my_thread1 ((background_task())) ; my_thread1.join (); thread my_thread2{background_task ()}; my_thread2.join ();
transfer
这段代码演示了如何使用std::thread
来创建新线程,并将类成员函数作为线程函数传递,同时也演示了如何使用std::unique_ptr
来管理资源,并将这个资源传递给一个新线程。
class X {public : void do_length_work () {}; }; void process_big_object (std::unique_ptr<X>) ;int main () { X my_x; thread t (&X::do_length_work, &my_x) ; std::unique_ptr<X> p (new X) ; p->do_length_work (); std::thread tt (process_big_object,std::move(p)) ; return 0 ; }
ownership
线程的创建、移动和管理
void some_function () {}void some_other_function () {}std::thread t1 (some_function) ; std::thread t2 = std::move (t1); t1 = std::thread (some_other_function); std::thread t3; t3 = std::move (t2); t1 = std::move (t3);
一组线程
这段代码使用了std::vector
和std::thread
,它展示了如何使用C++11标准库来创建一组线程并等待它们完成工作。
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)); }
RAII类scoped_thread
这是一个RAII类scoped_thread
的定义,它使用C++11标准库中的std::thread
类来封装线程对象。具体来说,scoped_thread
类通过重载构造函数和析构函数来控制线程的生命周期,从而确保线程在适当的时候被销毁。
在这个类的定义中,几个关键的实现细节:
使用explicit
防止隐式类型转换。
使用std::logic_error
来抛出异常,以表示线程没有被启动的错误情况。
在析构函数中调用join()
函数,以确保线程在scoped_thread
对象被销毁时能够正常结束。
除此之外,由于线程对象的复制和复制赋值操作不可重载,因此需要通过delete
关键字来禁止这些操作。
class scoped_thread { std::thread t; public : explicit scoped_thread (std::thread t_) : // 1 t(std::move(t_)) { if (!t.joinable ()) throw std::logic_error ("No thread" ); } ~scoped_thread () { t.join (); } scoped_thread (scoped_thread const &) = delete ; scoped_thread &operator =(scoped_thread const &) = delete ; };
runtime
简单的线程池:将迭代器范围分割成多个子范围,每个子范围由一个线程进行处理,最后合并所有子范围的累加结果,从而实现了并行的累加操作。
在实现上,函数首先根据范围长度和硬件线程数,计算出要使用的线程数和任务块的大小。然后,函数将序列分块,并为每个块创建一个线程来执行累加操作。最后,使用std::accumulate
将所有结果相加以得到最终结果。
#include <iostream> #include <algorithm> #include <thread> #include <vector> #include <numeric> #include <functional> using namespace std;template <typename Iterator, typename T>struct accumulate_block { void operator () (Iterator first, Iterator last, T &result) { result = std::accumulate (first, last, result); } }; template <typename Iterator, typename T>T parallel_accumlate (Iterator first, Iterator last, T init) { unsigned long const length = std::distance (first, last); if (!length) return init; unsigned long const min_per_thread = 25 ; unsigned long const max_threads = (length + min_per_thread - 1 ) / min_per_thread; cout<<max_threads<<endl; unsigned long const hardware_threads = std::thread::hardware_concurrency (); cout<<hardware_threads<<endl; unsigned long const num_threads = std::min (hardware_threads != 0 ? hardware_threads : 2 , max_threads); cout<<num_threads<<endl; unsigned long const block_size = length / num_threads; cout<<block_size<<endl; std::vector<T> results (num_threads) ; std::vector<std::thread> threads (num_threads - 1 ) ; Iterator block_start = first; for (unsigned long i = 0 ; i < (num_threads - 1 ); ++i) { Iterator block_end = block_start; std::advance (block_end, block_size); threads[i] = std::thread (accumulate_block <Iterator, T>(), block_start, block_end, std::ref (results[i])); block_start = block_end; } accumulate_block <Iterator, T>()(block_start, last, results[num_threads - 1 ]); std::for_each(threads.begin (), threads.end (), std::mem_fn (&std::thread::join)); return std::accumulate (results.begin (), results.end (), init); } int main () { vector<int > v{3 ,4 ,5 ,6 }; int res=0 ; cout<<parallel_accumlate (v.begin (),v.end (),res); return 0 ; }
id
代码定义了两个函数do_master_thread_work
和do_common_work
,分别用于处理主线程和其他线程(即非主线程)的工作。在some_core_part_of_algorithm
函数中,代码首先判断当前线程是否为主线程,如果是,则调用do_master_thread_work
函数,否则调用do_common_work
函数。
#include <iostream> #include <thread> using namespace std;std::thread::id master_thread; void do_master_thread_work () { cout << "master" << endl; } void do_common_work () { cout << "common" << endl; } void some_core_part_of_algorithm () { if (std::this_thread::get_id () == master_thread) { do_master_thread_work (); } do_common_work (); } int main () { master_thread = std::this_thread::get_id (); std::cout << "master_thread: " << master_thread << endl; cout << "master_thread 中运行:" << endl; some_core_part_of_algorithm (); cout << "thread 中运行:" << endl; thread t (some_core_part_of_algorithm) ; t.join (); return 0 ; }
Reference
[1] C++ 那些事: https://github.com/Light-City/CPlusPlusThings