北京轨道交通建设公司网站,随州网站建设有限公司,互联网做什么比较赚钱,星空无限mv国产剧转移线程的所有权
假设你想要编写一个函数#xff0c;它创建一个在后台运行的线程#xff0c;但是向调用函数回传新线程的所有权#xff0c;而非等待其完成#xff0c;又或者你想要反过来做#xff0c;创建一个线程#xff0c;并将所有权传递给要等待它完成的函数。在任…转移线程的所有权
假设你想要编写一个函数它创建一个在后台运行的线程但是向调用函数回传新线程的所有权而非等待其完成又或者你想要反过来做创建一个线程并将所有权传递给要等待它完成的函数。在任意一种情况下你都需要将所有权从一个地方转移到另一个地方。
这里就是std::thread支持移动的由来。正如在上一节所描述的在C标准库里许多拥有资源的类型如std::ifstream 和 std::unique_ptr是可移动的(movable)而非可复制的copyable)并且std::thread就是其中之一。这意味着一个特定执行线程的所有权可以在std::thread实例之间移动如同接下来的例子。该示例展示了创建两个执行线程以及在三个std::thread实例t1、t2和t3之间对那些线程的所有权进行转移。
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); //❻ 此赋值将终结程序首先启动一个新线程❶并与t1相关联。然后当t2构建完成时所有权被转移给t2通过调用std::move()来显式地转移所有权❷。此刻t1不再拥有相关联的执行线程运行some_function的线程现在与t2相关联。
然后启动一个新的线程并与一个临时的std::thread对象相关联❸。接下来将所有权转移到t1中是不需要调用std::move()来显式移动所有权的因为此处所有者是一个临时对象——从临时对象中进行移动是自动和隐式的。
t3是默认构造的❹这意味着它的创建没有任何相关联的执行线程。当前与t2相关联的线程的所有权转移到t3❺再次通过显式调用std::move()因为t2是一个命名对象。在所有这些移动之后t1与运行some_other_function 的线程相关联t2没有相关联的线程t3与运行some_function的线程相关联。
最后一次移动❻将运行 some_function的线程的所有权转回给t1。但是在这种情况下t1已经有了一个相关联的线程运行着some_other_function)所以会调用std::terminate()来终止程序。这样做是为了与std::thread的析构函数保持一致。你必须在析构前显式地等待线程完成或是分离这同样适用于赋值你不能仅仅通过向管理一个线程的std::thread对象赋值一个新的值来“舍弃”一个线程。
std::thread支持移动意味着所有权可以很容易地从一个函数中被转移出如清单2.5所示。
//清单2.5 从函数中返回std::thread
#include threadvoid some_function()
{}void some_other_function(int)
{}std::thread f()
{void some_function();return std::thread(some_function);
}
std::thread g()
{void some_other_function(int);std::thread t(some_other_function, 42);return t;
}int main()
{std::thread t1 f();t1.join();std::thread t2 g();t2.join();
}
同样地如果要把所有权转移到函数中它只能以值的形式接受std::thread的实例作为其中一个参数如下所示。
void f(std::thread t);void g()
{void some_other_function();f(std::thread(some_function));std::thread t(some_function);f(std::move(t));
}std::thread支持移动的好处之一就是你可以建立在清单2.3中thread_guard 类的基础上同时使它实际上获得线程的所有权。这可以避免thread_guard 对象在引用它的线程结束后继续存在所造成的不良影响同时也意味着一旦所有权转移到了该对象那么其他对象都不可以结合或分离该线程。因为这主要是为了确保在退出一个作用城之前线程都已完成我把这个类称为scoped_thread。其实现如清单2.6所示同时附带一个简单的示例。
#include thread
#include utilityclass scoped_thread
{std::thread t;
public:explicit scoped_thread(std::thread t_): //❶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;
};void do_something(int i)
{i;
}struct func
{int i;func(int i_):i(i_){}void operator()(){for(unsigned j0;j1000000;j){do_something(i);}}
};void do_something_in_current_thread()
{}void f()
{int some_local_state;scoped_thread t(std::thread(func(some_local_state))); //❹do_something_in_current_thread();
} //❺int main()
{f();
}
这个例子与清单2.3类似但是新线程被直接传递到scoped_thread❹而不是为它创建一个单独的命名变量。当初始线程到达 f❺ 的结尾时scoped_thread对象被销毁然后结合❸提供给构造函数❶的线程。使用清单2.3中的thread_guard类析构函数必须检查线程是不是仍然可结合你可以在构造函数中❷来做如果不是则引发异常。
std::thread对移动的支持同样考虑了std::thread对象的容器如果那些容器是移动感知的(如更新后的std::vector)。这意味着你可以编写像清单2.7中的代码生成一批线程然后等待它们完成。
#include vector
#include thread
#include algorithm
#include functionalvoid do_work(unsigned id)
{}void f()
{std::vectorstd::thread threads;for(unsigned i0; i20; i){threads.push_back(std::thread(do_work, i)); //生成线程}std::for_each(threads.begin(), threads.end(),std::mem_fn(std::thread::join)); //轮流在每个线程上调用join()
}int main()
{f();
}
如果线程是被用来细分某种算法的工作这往往正是所需的。在返回调用者之前所有线程必须全都完成。当然清单2.7的简单结构意味着由线程所做的工作是自包含的同时它们操作的结果纯粹是共享数据的副作用。如果f()向调用者返回一个依赖于这些线程的操作结果的值那么正如所写的这样该返回值就得通过检查线程终止后的共享数据来决定。
将std::thread对象放到std::vector中是线程迈向自动管理的一步。与其为那些线程创建独立的变量并直接与之结合不如将它们视为群组。你可以进一步创建在运行时确定的动态数量的线程更进一步地利用这一步而不是如清单2.7中的那样创建固定的数量。