《C++并发编程实战》之第2章 线程管理
2.1 基本管理
2.1.1 启动线程
- thread对象创建时,立即启动线程。
void do_some_work();
//创建thread对象t的同时,立即运行函数do_some_work()。
thread t(do_some_work);
- 任何可调用(callable)类型可用于创建thread。
class background_task {
public:
void operator()() const {
do_something();
do_something_else();
}
};
background_task f;
thread t(f); //f被复制到子线程中执行。
//二义性C++语句,可能被解释成函数声明时,编译器就这样解释
//错误
//函数t,返回thread,接受函数指针参数
//该函数指针无输入返回background_task对象。
thread t(background_task());
//正确
thread t((background_task()));
thread t{background_task()};
thread t([]{ //lambda表达式
do_something();
do_something_else();
});
- 线程函数持有局部变量的指针或引用,容易出错。
struct func {
int &i;
func(int &i_):i(i_){}
void operator()() {
for(unsigned j=0; j<1000000; j++) {
do_something(i);
}
}
};
void oops() {
int some_local_state=0;
func my_func(some_local_state);
thread t(my_func);
//不等待t线程完成
//oops()函数退出线程后,局部变量some_local_state会被释放
//t线程访问局部变量some_local_state会出错。
//t.detach();
//等待t线程结束,局部变量some_local_state不会被释放
t.join();
cout <<"some_local_state:" <<some_local_state <<endl;
}
2.1.cpp
#include <thread>
#include <iostream>
using namespace std;
void do_something(int &i)
{
++i;
}
struct func
{
int &i;
func(int &i_) : i(i_) {}
void operator()()
{
for (unsigned j = 0; j < 1000000; ++j)
do_something(i);
}
};
void oops()
{
int some_local_state = 0;
func my_func(some_local_state);
std::thread my_thread(my_func);
// my_thread.detach();
my_thread.join();
cout << "some_local_state:" << some_local_state << endl;
}
int main()
{
oops();
return 0;
}
2.1.2 等待线程
join()要么等待一个线程完成,要么不等。
只能对给定线程调用一次join(),调用后joinable()返回false。
2.1.3 异常线程
必须确保thread对象被销毁前已调用join()或detach()。
线程启动(创建thread对象)后,可以立即调用detach(),不是问题。
无论程序运行是否异常,确保join()的调用。
2.2.cpp
#include <thread>
#include <iostream>
using namespace std;
void do_something(int& i) {
++i;
}
struct func {
int& i;
func(int& i_):i(i_){}
void operator()() {
for(unsigned j=0; j<1000000; ++j)
do_something(i);
}
};
void do_something_in_current_thread() {}
int some_local_state = 0;
void f() {
func my_func(some_local_state);
std::thread t(my_func);
try {
do_something_in_current_thread();
} catch(...) {
t.join();
throw;
}
t.join();
}
int main() {
f();
cout <<"some_local_state:" <<some_local_state <<endl;
return 0;
}
资源获取即初始化(RAII)
2.3.cpp
#include <thread>
#include <iostream>
using namespace std;
class thread_guard
{
std::thread &t;
public:
explicit thread_guard(std::thread &t_) : t(t_)
{
cout << "thread_guard\n";
}
~thread_guard()
{
cout << "~thread_guard\n";
if (t.joinable())
t.join();
}
thread_guard(thread_guard const &) = delete;
thread_guard &operator=(thread_guard const &) = delete;
};
void do_something(int &i)
{
++i;
}
struct func
{
int &i;
func(int &i_) : i(i_) {}
void operator()()
{
for (unsigned j = 0; j < 1000000; ++j)
do_something(i);
}
};
void do_something_in_current_thread() {}
void f(int &some_local_state)
{
func my_func(some_local_state);
std::thread t(my_func);
thread_guard g(t);
do_something_in_current_thread();
}
int main()
{
int some_local_state = 0;
{
f(some_local_state);
}
cout << "some_local_state:" << some_local_state << endl;
return 0;
}
2.1.4 后台线程
thread调用detach()后在后台运行(守护线程),无法直接与之通信;所有权和控制权被转交给C++运行时库,确保线程关联资源在线程退出后被正确回收。
thread调用detach()分离线程后,不在与线程关联,也不能够被加入。
在这种情况下,当主线程退出后,相关的子线程及派生线程会退出。
test.cpp
#include <Windows.h>
#include <iostream>
#include <thread>
#include <fstream>
using namespace std;
void independentThread()
{
Sleep(500);
ofstream os("test.txt", ios::trunc);
if (os.is_open())
{
os << "Hello\n";
os.close();
}
}
void threadCaller()
{
thread myThread(independentThread);
cout << "ID: " << myThread.get_id() << endl;
myThread.detach();
Sleep(200);
}
int main()
{
threadCaller();
// Sleep(1000);
return 0;
}
值得注意的是,如果你决定不等待线程完成,那么你需要确保通过该线程访问的数据是有效的,直到该线程完成为止。
使用分离线程的意义通常是让它成为一个守护线程(daemon threads)。它无需任何显式的用户界面,而长时间运行在后台,执行后台任务,例如监控文件系统、优化数据结构、清除缓存等等。
thread t(do_background_work);
t.detach();
assert(!t.joinable());
同一个应用程序实例启动新线程处理新文档
2.4.cpp
#include <thread>
#include <string>
#include <iostream>
using namespace std;
void open_document_and_display_gui(string const &filename) {}
bool done_editing() { return false; }
enum command_type
{
open_new_document
};
struct user_command
{
command_type type;
user_command() : type(open_new_document) {}
};
user_command get_user_input() { return user_command(); }
string get_filename_from_user()
{
static int n;
return "file" + to_string(++n) + ".doc";
}
void process_user_input(user_command const &cmd) {}
void edit_document(string const &filename)
{
cout << filename << endl;
open_document_and_display_gui(filename);
// while (!done_editing())
for (int i = 0; i < 2; i++)
{
user_command cmd = get_user_input();
if (cmd.type == open_new_document)
{
string const new_name = get_filename_from_user();
thread t(edit_document, new_name);
t.detach();
}
else
{
process_user_input(cmd);
}
}
}
int main()
{
edit_document("bar.doc");
this_thread::sleep_for(chrono::milliseconds(1));
return 0;
}
2.2 线程传参
线程具有内部存储空间,参数默认按照复制方式(当成副本)传递,即使函数形参为引用。
void f(int i, string const &s);
thread t(f, 3, "hello");
//进入线程上下文后,const char *参数("hello")才转换为string类型。
void f(int i, string const &s);
void oops(int some_param) {
char buffer[1024];
sprintf(buffer, "%i", some_param);
//thread t(f, 3, buffer);
//char(buffer)转string可能未及时发生
//局部数组buffer销毁,可能导致未定义行为
//string(buffer),子线程启动前
//主线程保证生成string对象副本并复制到子线程空间。
thread t(f, 3, string(buffer));
t.detach();
}
void update_data_for_widget(widget_id w, widget_data &data);
void oops_again(widget_id w) {
widget_data data;
//data复制副本,非引用。
//thread t(update_data_for_widget, w, data);
//data传递引用。
thread t(update_data_for_widget, w, std::ref(data));
display_status();
t.join();
process_widget_data(data);
}
class X {
public:
void do_work();
};
X x;
thread t(&X::do_work, &x);
void process_big_object(std::unique_ptr<big_object>);
std::unique_ptr<big_object> p(new big_object);
p->prepare_data(42);
thread t(process_big_object, std::move(p)); //p及big_object被转移到线程内。
2.3 线程转移
void some_function();
void some_other_function();
thread t1(some_function); //新线程关联t1
thread t2 = std::move(t1);//新线程归属权转移到t2
t1 = thread(some_other_function); //启动另一新线程
thread t3;//未关联任何线程
t3 = std::move(t2); //t2关联线程归属权转移到t3
//t1 = std::move(t3);//错误
//t1正管控线程,此时赋值,该线程会terminate(),会被遗弃,程序终止。
函数内部返回thread对象
2.5.cpp
#include <thread>
#include <iostream>
using namespace std;
void some_function() {}
void some_other_function(int) {}
thread f() { return thread(some_function); }
thread g()
{
thread t(some_other_function, 42);
return t;
}
void f2(thread t)
{
if (t.joinable())
t.join();
}
void g2()
{
f2(thread(some_function));
thread t(some_function);
f2(move(t));
thread t2;
t2 = g();
//f2(t2);
f2(move(t2));
thread t3{g()};
t3.join();
cout << "g2 over!\n";
}
int main()
{
thread t1 = f();
t1.join();
g2();
thread t2 = g();
t2.join();
return 0;
}
scoped_thread类及用例
2.6.cpp
#include <thread>
#include <utility>
#include <stdexcept>
#include <iostream>
using namespace std;
void do_something(int &i) { ++i; }
struct func
{
int &i;
func(int &i_) : i(i_) {}
void operator()()
{
for (unsigned j = 0; j < 1000000; ++j)
do_something(i);
}
};
class scoped_thread
{
thread t;
public:
explicit scoped_thread(thread t_) : t(move(t_))
{
cout << "scoped_thread" << endl;
if (!t.joinable())
throw logic_error("No thread");
}
~scoped_thread()
{
cout << "~scoped_thread" << endl;
if (t.joinable())
t.join();
}
scoped_thread(scoped_thread const &) = delete;
scoped_thread &operator=(scoped_thread const &) = delete;
};
void do_something_in_current_thread() {}
void f(int &some_local_state)
{
// scoped_thread t(thread(func(some_local_state)));
scoped_thread t(thread{func(some_local_state)});
do_something_in_current_thread();
}
void f2(int &some_local_state)
{
scoped_thread t{thread(func(some_local_state))};
do_something_in_current_thread();
}
int main()
{
int some_local_state = 0;
f(some_local_state);
f2(some_local_state);
cout << "some_local_state: " << some_local_state << endl;
return 0;
}
joining_thread类
2.7.cpp
#include <thread>
#include <iostream>
using namespace std;
class joining_thread
{
std::thread t;
public:
joining_thread() noexcept = default;
template <typename Callable, typename... Args>
explicit joining_thread(Callable &&func, Args &&...args) : t(std::forward<Callable>(func), std::forward<Args>(args)...) {}
explicit joining_thread(std::thread t_) noexcept : t(std::move(t_)) {
cout<<"joining_thread"<<endl;
}
joining_thread(joining_thread &&other) noexcept : t(std::move(other.t)) {}
joining_thread &operator=(joining_thread &&other) noexcept
{
if (joinable())
join();
t = std::move(other.t);
return *this;
}
joining_thread &operator=(std::thread other) noexcept
{
if (joinable())
join();
t = std::move(other);
return *this;
}
~joining_thread() noexcept
{
cout<<"~joining_thread"<<endl;
if (joinable())
join();
}
void swap(joining_thread &other) noexcept { t.swap(other.t); }
std::thread::id get_id() const noexcept { return t.get_id(); }
bool joinable() const noexcept { return t.joinable(); }
void join() { t.join(); }
void detach() { t.detach(); }
std::thread &as_thread() noexcept { return t; }
const std::thread &as_thread() const noexcept { return t; }
};
void func()
{
std::cout << "func!\n";
}
int main()
{
{
joining_thread jt{std::thread(func)};
}
std::cout << "Over!\n";
return 0;
}
等待多线程完成运行
2.8.cpp
#include <vector>
#include <thread>
#include <algorithm>
#include <functional>
#include <iostream>
using namespace std;
void do_work(unsigned id) {}
void f(unsigned n)
{
std::vector<std::thread> threads;
for (unsigned i = 0; i < n; ++i)
// threads.push_back(std::thread(do_work, i));
threads.emplace_back(std::thread(do_work, i));
// std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread::join));
for (auto &entry : threads)
entry.join();
}
void do_work2(int *data, unsigned s_id, unsigned e_id, int &result)
{
result = 0;
for (unsigned i = s_id; i <= e_id; i++)
result += data[i];
}
int f2(int *data, unsigned len, unsigned n)
{
vector<int> results;
results.resize(n);
std::vector<std::thread> threads;
for (unsigned i = 0; i < n; ++i)
{
unsigned s_id = len * i / n;
unsigned e_id = len * (i + 1) / n - 1;
threads.push_back(std::thread(do_work2, data, s_id, e_id, std::ref(results[i])));
}
std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread::join));
int result = 0;
for (unsigned i = 0; i < n; ++i)
result += results[i];
return result;
}
int main()
{
unsigned n = 20;
f(n);
int data[100];
unsigned len = 100;
for (unsigned i = 0; i < len; ++i)
data[i] = i;
int result = f2(data, len, n);
cout << "result:" << result << endl;
cout << "over!\n";
return 0;
}
2.4 线程数量
2.9.cpp
#include <thread>
#include <numeric>
#include <algorithm>
#include <functional>
#include <vector>
#include <iostream>
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_accumulate(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; //最大线程数量
unsigned long const hardware_threads = std::thread::hardware_concurrency(); //硬件线程数量
unsigned long const num_threads = std::min(hardware_threads != 0 ? hardware_threads : 2, max_threads); //实际运行线程数量
unsigned long const block_size = length / num_threads; //各线程分担数量
std::vector<T> results(num_threads);
std::vector<std::thread> threads(num_threads - 1); //发起线程数量,parallel_accumulate()函数占据一个线程
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()
{
std::vector<int> vi;
for (int i = 0; i < 10; ++i)
vi.push_back(10);
int sum = parallel_accumulate(vi.begin(), vi.end(), 5);
std::cout << "sum = " << sum << std::endl;
return 0;
}
2.5 识别线程
std::thread::id
- thread对象调用成员函数get_id(),零值(未关联线程)或唯一值。
- 函数内调用std::this_thread::get_id()。
std::thread::id master_thread;//主线程中this_thread::get_id()赋值
void some_core_part_of_algorithm() {
if(std::this_thread::get_id() == master_thread)
do_master_thread_work();
do_common_work();
}