C++并发编程学习——2.管理线程

基本线程管理
(1)每个C++程序至少有一个线程,它在运行程序时启动,运行着main函数。
当线程运行完它的初始函数时,该线程就会退出。
(2)启动线程
线程是通过构造 std:thread 对象 来开始的。
void do_some_work();   //函数
std::thread my_thread(do_some_work);  //thread对象
thread构造函数可以传入任何的可调用对象 ,例如:
class background_task
{
public:
	void operator()() const
	{
		do_something();
	}
};
background_task f;
std::thread my_thread(f);
一旦开始了线程,必须要显示的决定是等待它完成( join ),还是让它自行运行( detach )。如果在thread对象被销毁前还没有决定(调用join或者detach),那么程序会被终止(thread的析构函数会调用std::terminate())。
/*
 * 代码2.1
 */
struct func
{
  int &i; //注意,传入的是引用。所以i的生命周期和func对象无关
  func(int &i_) : i(i_){}
  void operator()()
  {
    for (unsigned j = 0; j < 100000; 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线程可能还在运行,但是some_local_state已经被销毁
}
(2)等待线程完成
如果需要等待线程完成,通过thread对象调用 join() 来实现。调用join()对象的线程会阻塞直到该线程返回。
一个线程只能调用一次join(),调用后调用 joinable() 返回false,即该thread对象无法再次调用join()。
(3)异常环境下的等待
如果要分离线程,那么通过线程启动后就可以分离线程。但是对于join线程,要考虑选择在哪个位置调用join()。
在异常环境下,对于存在异常时也要调用join,防止因为异常而跳过了join。
但是这样使用容易把作用域弄乱,所以不是一个理想的方案。更好的方法是使用资源获取即初始化(RAII),提供一个类,在它的析构函数中进行join()。
class thread_gurad
{
  std::thread &t;
public:
  thread_gurad(std::thread &t_) : t(t_){}
  ~thread_gurad()
  { //析构时调用join,保证线程的退出
    if (t.joinable())
    {
      t.join();
    }
  }
  thread_gurad(thread_gurad cosnt &) = delete;
  thread_gurad& operator=(thread_gurad const &) = delete;
};
struct func;
void f()
{
  int some_local_state = 0;
  func my_func(some_local_state);
  std::thread t(my_func);
  thread_gurad g(t);
  do_some_thing();
  //先销毁g,销毁g时调用了t.join(),保证了线程退出
  //线程执行完再销毁了some_local_state
}
(4)在后台运行线程
在thread对象调用detach(),会把线程丢在后台运行,没用方法通信。被分离的线程通常称为守护线程,无需用户界面,只运行在后台。
只能在t.joinable()返回true时,在能调用detach()。
传递参数给线程函数
(1)传递参数给线程的启动函数,就是传递给thread对象的构造函数。
参数会以默认的方式被复制(copy)传递给线程的存储空间,从而被线程访问,即使参数类型是引用。 (和std::bind传递参数的传递方式相同)
void f(int i, std::string const &s);
std::thread t(f, 3, "hello"); //传递了参数
因此有些情况下,你想到的是引用,但是对象只会被复制,例如:
void update_data_for_widget(widget_id w, widget_t &data);
void oops_again(widget_t w)
{
  widget_data data;
  std::thread t(update_data_for_widget, w, data); //传递的是data的复制的引用!
  display_status();
  t.join();
  process_widget_data(data); //data值没有改变
}
当对thread对象传入data时,传递给新线程的是data在内部的副本的引用而不是对data本身的引用,所以在oops_again中data的值不会被t线程改变。
可以使用std::ref来包装确实需要被引用的参数:
std::thread t(update_data_for_widget, w, std::ref(data));
转移线程的所有权
(1)std::thread的实例对象只能是 可移动的(move),是不可复制的(copy) 。这确保了任意时刻只有一个对象与某个特定的执行线程相关联,即线程只有一条执行路径。(类似std::ifstream,std::unique_ptr)
std::thread对移动的支持同时考虑了std::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()
}
在运行时选择线程数量
std::thread::hardware_currency() ,这个函数返回一个对于给定程序执行时能够真正并发的线程数量的知识。例如,在多核系统上它可能返回的是CPU的数量,返回0表示信息不可用。
标识线程
线程标识符是 std::thread::id类型 的。线程标识符可以通过thread对象调用 get_id()函数 来返回。另外,当前执行的线程标识符可以使用 std::this_thread::get_id() 获得。
std::thread::id类型的对象可以自由的复制和比较。如果二个线程标识符相等,代表他们代表同一个线程或者都没有相关线程。
std::thread::id的实例常常用于检查一个线程是否需要执行某些操作。
if(std::this_thread::get_id() == master_thread)
{
do_master_thread_work();
}


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++ ,我们可以使用线程库来实现多线程编程。线程的挂起、唤醒与终止是多线程编程非常重要的一部分。 线程的挂起也称为线程的休眠,它可以让线程停止运行一段时间,等待某个条件满足后再继续运行。在 C++ ,我们可以使用 std::this_thread::sleep_for() 函数来实现线程的挂起,该函数可以让当前线程挂起一段时间,例如: ```cpp #include <chrono> #include <thread> int main() { // 挂起当前线程 1 秒钟 std::this_thread::sleep_for(std::chrono::seconds(1)); return 0; } ``` 线程的唤醒可以通过条件变量来实现,条件变量是一种同步机制,用于在线程之间传递信号。在 C++ ,我们可以使用 std::condition_variable 类来创建条件变量,然后使用 wait() 函数来挂起线程等待条件变量的信号,使用 notify_one() 函数来唤醒一个等待条件变量的线程,例如: ```cpp #include <condition_variable> #include <mutex> #include <thread> std::condition_variable cv; std::mutex mtx; bool ready = false; void worker_thread() { // 等待条件变量的信号 std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, [](){ return ready; }); // 条件满足后继续执行 // ... } int main() { // 唤醒等待条件变量的线程 { std::lock_guard<std::mutex> lock(mtx); ready = true; } cv.notify_one(); return 0; } ``` 线程的终止可以使用 std::thread::join() 函数来实现,该函数可以让当前线程等待另一个线程执行完成后再继续执行,例如: ```cpp #include <thread> void worker_thread() { // ... } int main() { std::thread t(worker_thread); // 等待 worker_thread 执行完成 t.join(); return 0; } ``` 另外,线程的终止还可以使用 std::thread::detach() 函数来实现,该函数可以让当前线程与创建的线程分离,使得两个线程可以独立运行,例如: ```cpp #include <thread> void worker_thread() { // ... } int main() { std::thread t(worker_thread); // 分离线程,使得两个线程可以独立运行 t.detach(); return 0; } ``` 需要注意的是,分离线程后,主线程不能再使用 join() 函数等待子线程执行完成,否则会导致程序崩溃。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值