C++11 多线程 —— 线程基础管理

1. 线程基础管理

Every C++ program has at least one thread, which is started by the C++ runtime: the thread running main(). Your program can then launch additional threads that have another function as the entry point. These threads then run concurrently with each other and with the initial thread.

1.1 启动线程

  • 怎么启动线程?)Threads are started by constructing a std::thread object that specifies the task to run on that thread. 例如,

    void do_some_work();
    std::thread my_thread(do_some_work);
    
  • std::thread 支持所有可调用类型std::thread works with any callable type, so you can pass an instance of a class with a function call operator to the std::thread constructor instead:

    class background_task {
    public:
    	void operator()() const {
    		do_something();
    		do_something_else();
    	}
    };
    
    background_task f;
    std::thread my_thread(f);
    
    • 将对象复制到线程自己的空间)In this case, the supplied function object is copied into the storage belonging to the newly created thread of execution and invoked from there.

    • 传入临时的函数对象时产生的问题)When passing a function object to the thread constructor, if you pass a temporary rather than a named variable, then the syntax can be the same as that of a function declaration, in which case the compiler interprets it as such, rather than an object definition.
      For example,

      std::thread my_thread(background_task());
      

      declares a function my_thread that takes a single parameter (of type pointer to a function taking no parameters and returning a background_task object) and returns a std::thread object, rather than launching a new thread.

      解决办法:使用命名变量替换临时变量,或使用额外的一对圆括号,或使用花括号初始化语法。

      std::thread my_thread((background_task()));			// 使用额外的一对圆括号
      std::thread my_thread{ background_task() };			// 使用花括号初始化语法
      

      One type of callable object that avoids this problem is a lambda expression.

      std::thread my_thread([](
      	do_something();
      	do_something_else();
      });
      
  • join/detach线程)Once you’ve started your thread, you need to explicitly decide whether to wait for it to finish (by joining with it) or leave it to run on its own (by detaching it). If you don’t decide before the std::thread object is destroyed, then your program is terminated (the std::thread destructor calls std::terminate()).

  • 保证数据有效性)If you don’t wait for your thread to finish, then you need to ensure that the data accessed by the thread is valid until the thread has finished with it.
    One common way to handle this scenario is to make the thread function self-contained and copy the data into the thread rather than sharing the data.
    Alternatively, you can ensure that the thread has completed execution before the function exits by joining with the thread.

1.2 等待线程结束

  • 怎么做?)If you need to wait for a thread to complete, you can do this by calling join() on the associated std::thread instance. 例如,

    void hello() {
    	std::cout<<"Hello Concurrent World\n";
    }
    
    int main() {
    	std::thread t(hello);
    	t.join();		// 等待线程结束
    }
    
  • join() 函数的工作join() is simple and brute force — wait for a thread to finish. The act of calling join() also cleans up any storage associated with the thread, so the std::thread object is no longer associated with the now-finished thread; it isn’t associated with any thread. This means that you can call join() only once for a given thread; once you’ve called join(), the std::thread object is no longer joinable, and joinable() will return false.


1.3 在异常环境中等待线程结束

  • 通常情况下的做法)In general, if you were intending to call join() in the non-exceptional case, you also need to call join() in the presence of an exception to avoid accidental lifetime problems. 例如,

    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 {
    		do_something_in_current_thread();
    	}
    	catch(...) {
    		t.join();	// 异常情况下join
    		throw;
    	}
    	t.join();		// 正常情况下join
    }
    
  • 使用 RAII idiom) One way of doing this is to use the standard Resource Acquisition Is Initialization (RAII) idiom and provide a class that does the join() in its destructor, as in the following listing.

    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;
    };
    
    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();
    }
    

    When the execution of the current thread reaches the end of f, the local objects are destroyed in reverse order of construction. Consequently, the thread_guard object g is destroyed first, and the thread is joined with in the destructor. This even happens if the function exits because do_something_in_current_thread throws an exception.


1.4 在后台运行线程

  • 怎么做?)Calling detach() on a std::thread object leaves the thread to run in the background. 例如,

    void edit_document(std::string const& filename) {
    
    	open_document_and_display_gui(filename);
    	
    	while(!done_editing()) {
    		user_command cmd=get_user_input();
    		if(cmd.type==open_new_document) {
    			std::string const new_name=get_filename_from_user();
    			std::thread t(edit_document,new_name);
    			t.detach();				// detach线程
    		}
    		else {
    			process_user_input(cmd);
    		}
    	}
    }
    
  • detach之后)If a thread becomes detached, it isn’t possible to obtain a std::thread object that references it, so it can no longer be joined(joinable() 返回 false). Detached threads truly run in the background; ownership and control are passed over to the C++ Runtime Library, which ensures that the resources associated with the thread are correctly reclaimed when the thread exits.

参考资料:C++ Concurrency in action.

猜你喜欢

转载自blog.csdn.net/fcku_88/article/details/88094363