《C++并发编程实战》之第2章 线程管理

130 篇文章 4 订阅
92 篇文章 19 订阅

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();
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值