高效swap 高效move 牛了

thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。

join是阻塞当前线程,并等待object对应线程结束,该线程继续执行搜索
detach是将线程从当前线程分离出去,即不受阻塞,操作系统会将其独立对待

多线程编程-detach

背景

线程1中获取任务之后,需要做长时间的处理,但是线程1不能够长时间等待,需要在获取完任务之后,就反馈信息。

方案设计:

线程1在接受任务之后,新建一个线程,并调用detach(),使该线程分离,允许独立运行。 
代码:

//新建一个线程的方式,用于处理其他任务
void sayhello()
{
    //模拟线程处理任务
    sleep(30);
    std::cout<<"son thread start process!"<<std::endl;
    sleep(30);
    std::cout<<"son thread process end!"<<std::endl;
}
int TestMulthread()
{
    boost::thread testhread(&sayhello);
    testhread.detach();
    return 10;
}
int main()
{
    int a = TestMulthread();
    std::cout<<"输出:a="<<a<<std::endl;
    sleep(180);
    return 0;
}

运行结果如下图所示: 
从中可以看到,调用TestMulthread的时候,在该函数内部,通过新建一个线程,调用sayhello函数,同时并直接返回10。在之后,才陆续打印出处理任务sayhello期间的信息。 
这里写图片描述

=====================================================================================================================================================================

【原创】C++之自定义高效的swap(1)

1 问题背景

    当交换两个包含了指针成员的类,我们最想看到的是直接交换其指针。但是当我们调用std::swap标准库这个模板函数时,通常它都会复制3个指针指向的对象作为交换所用,缺乏效率。如下:

复制代码

1 namespace std{
2     template<typename T>
3     void swap(T& a, T& b) //std::swap的典型实现
4     {
5         T temp(a);    //一次拷贝,两次赋值
6         a = b;
7         b = temp;
8     }
9 }

复制代码

    上面的代码,5行的调用了类的拷贝构造函数将a拷贝给temp,6、7行调用了拷贝赋值函数交换a、b对象。

    那么我们能不能自定义一个较高效率的属于我们自己类的swap函数呢?

2 自定义高效的swap函数

    我们可以为自己写的新类T提供一个高效的swap方法。一般来说,提供swap方法有两种。

2.1 swap成员函数

2.1.1 方法

(1)在我们写的类T中添加一个swap成员函数,这样方便我们交换类中的私有成员,并且设置swap函数不会抛出异常,为什么?见《C++ Primer 第五版中文版》474页。

void T::swap(T& t) noexcept;

(2)在类T的同一命名空间里添加一个非成员函数swap,用于调用类中的成员函数swap

1 void swap(T& a, T& b) noexcept
2 {
3     a.swap(b);
4 }

2.1.2 典型实现

复制代码

 1 #include <iostream>
 2 #include <string>
 3 class ClassTest{
 4 public:
 5     friend std::ostream& operator<<(std::ostream &os, const ClassTest& s);
 6     friend void swap(ClassTest &a, ClassTest &b) noexcept;
 7     ClassTest(std::string s = "abc") :str(new std::string(s)){} //默认构造函数
 8     ClassTest(const ClassTest &ct) :str(new std::string(*ct.str)){} //拷贝构造函数
 9     ClassTest &operator=(const ClassTest &ct) //拷贝赋值函数
10     {
11         str = new std::string(*ct.str);
12         return *this;
13     }
14     ~ClassTest() //析构函数
15     {
16         delete str;
17     }
18     void swap(ClassTest &t) noexcept
19     {
20         using std::swap;
21         swap(str,t.str); //交换指针,而不是string数据
22     }
23 private:
24     std::string *str;  //一个指针资源
25 };
26 std::ostream& operator<<(std::ostream &os,const ClassTest& s)
27 {
28     os << *s.str;
29     return os;
30 }
31 void swap(ClassTest &a, ClassTest &b) noexcept
32 {
33     a.swap(b);
34 }
35 int main(int argc, char const *argv[])
36 {
37     ClassTest ct1("ct1");
38     ClassTest ct2("ct2");
39     std::cout << ct1 << std::endl;
40     std::cout << ct2 << std::endl;
41     swap(ct1, ct2);
42     std::cout << ct1 << std::endl;
43     std::cout << ct2 << std::endl;
44     return 0;
45 }

复制代码

注意:

  1. (2)中的swap函数需要和类T位于同一的命名空间里,否则外部调用swap函数可能会解析不到
  2. swap函数最好使它不要抛出异常,就像移动构造函数和移动赋值函数一样。
  3. (2)中的函数可以声明为类T的友元函数,并且设置为内联函数
  4. 做真实交换的swap函数,需要使用using std::swap;

2.1.2 关于using std::swap

1 void swap(ClassTest &t) noexcept
2 {
3     using std::swap;
4     swap(str, t.str); //交换指针,而不是string数据
5 }

    在这里为什么要使用using std::swap呢?也就是using std::swap的作用是什么?

    在C++里存在多种名字查找规则:

  1. 普通的名字查找:先在当前的作用域里查找,查找不到再到外层作用域中查找,即局部相同名字的变量或函数会隐藏外层的变量或作用域
  2. 实参相关的名字查找(ADL)(链接2)
  3. 涉及函数模板匹配规则:一个调用的候选函数(关于候选函数请参考C++ Primer第五版中关于函数的一章)包括所有模板实参推断成功的函数模板实例;候选的函数模板总是可行的,因为模板实参推断会排除任何不可行的模板;如果恰好有一个函数(或模板)比其他函数更加匹配,则选择该函数;同样好的函数里对于有多个函数模板和只有一个非模板函数,会优先选择非模板函数;同样好的函数里对于没有非模板函数,那么选择更特例化的函数模板
  4. 因为C++会优先在当前的作用域里查找,所以使用using std::swap将标准库的swap模板函数名字引入该局部作用域,重载当前作用域的同名函数,隐藏外层作用域的相关声明。为什么using std::swap不会隐藏外层的void swap(ClassTest &a, ClassTest &b) noexcept函数呢?参见:这里。其中说到,当经过普通的名字查找后(没有包括ADL),如果候选函数中有类成员、块作用域中的函数声明(不包括using声明引入的)、其他同名的函数对象或变量名,则不启动ADL查找了。如果没有,则进行ADL查找。因此在经过普通的查找后,发现并没有匹配的函数,最后再经过ADL找到了标准库中的swap和外层作用域的void swap(ClassTest &a, ClassTest &b) noexcept,由于后者较匹配,编译器优先选择后者。
  5. 如果str类型有自定义的swap函数,那么第4行代码的swap调用将会调用str类型自定义的swap函数
  6. 但是如果str类型并没有特定的swap函数,那么第4行代码的swap调用将会被解析到标准库的std::swap

2.2 swap友元函数

2.2.1 方法

(1)在T的同一命名空间中定义一非成员的swap函数,并且将函数声明为类T的友元函数,方便访问T的私有成员

1 void swap(ClassTest &a, ClassTest &b) noexcept
2 {
3     using std::swap;
4     //swap交换操作
5 }

2.2.2 典型实现

复制代码

 1 #include <iostream>
 2 #include <string>
 3 class ClassTest{
 4 public:
 5     friend std::ostream& operator<<(std::ostream &os, const ClassTest& s);
 6     friend void swap(ClassTest &a, ClassTest &b) noexcept;
 7     ClassTest(std::string s = "abc") :str(new std::string(s)){} //默认构造函数
 8     ClassTest(const ClassTest &ct) :str(new std::string(*ct.str)){} //拷贝构造函数
 9     ClassTest &operator=(const ClassTest &ct) //拷贝赋值函数
10     {
11         str = new std::string(*ct.str);
12         return *this;
13     }
14     ~ClassTest() //析构函数
15     {
16         delete str;
17     }
18 private:
19     std::string *str;  //一个指针资源
20 };
21 std::ostream& operator<<(std::ostream &os, const ClassTest& s)
22 {
23     os << *s.str;
24     return os;
25 }
26 void swap(ClassTest &a, ClassTest &b) noexcept
27 {
28     using std::swap;
29     swap(a.str,b.str); //交换指针,而不是string数据
30 }
31 int main(int argc, char const *argv[])
32 {
33     ClassTest ct1("ct1");
34     ClassTest ct2("ct2");
35     std::cout << ct1 << std::endl;
36     std::cout << ct2 << std::endl;
37     swap(ct1, ct2);
38     std::cout << ct1 << std::endl;
39     std::cout << ct2 << std::endl;
40     return 0;
41 }

复制代码

注意:

  1. (2)中的swap函数需要和类T位于同一的命名空间里,否则外部调用swap函数可能会解析不到
  2. swap函数最好使它不要抛出异常,就像移动构造函数和移动赋值函数一样

=====================================================================================================================================================================

c++11 std::move() 的使用

std::move函数可以以非常简单的方式将左值引用转换为右值引用。(左值、左值引用、右值、右值引用 参见:http://www.cnblogs.com/SZxiaochun/p/8017475.html

通过std::move,可以避免不必要的拷贝操作。

std::move是为性能而生。

std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝。

如string类在赋值或者拷贝构造函数中会声明char数组来存放数据,然后把原string中的 char 数组被析构函数释放,如果a是一个临时变量,则上面的拷贝,析构就是多余的,完全可以把临时变量a中的数据直接 “转移” 到新的变量下面即可。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

#include <iostream>

#include <utility>

#include <vector>

#include <string>

int main()

{

    std::string str = "Hello";

    std::vector<std::string> v;

    //调用常规的拷贝构造函数,新建字符数组,拷贝数据

    v.push_back(str);

    std::cout << "After copy, str is \"" << str << "\"\n";

    //调用移动构造函数,掏空str,掏空后,最好不要使用str

    v.push_back(std::move(str));

    std::cout << "After move, str is \"" << str << "\"\n";

    std::cout << "The contents of the vector are \"" << v[0]

                                         << "\", \"" << v[1] << "\"\n";

}

==============================================================================================================================================================================================

上一讲《C++11 并发指南四(<future> 详解一 std::promise 介绍)》主要介绍了 <future> 头文件中的 std::promise 类,本文主要介绍 std::packaged_task。

std::packaged_task 包装一个可调用的对象,并且允许异步获取该可调用对象产生的结果,从包装可调用对象意义上来讲,std::packaged_task 与 std::function 类似,只不过 std::packaged_task 将其包装的可调用对象的执行结果传递给一个 std::future 对象(该对象通常在另外一个线程中获取 std::packaged_task 任务的执行结果)。

std::packaged_task 对象内部包含了两个最基本元素,一、被包装的任务(stored task),任务(task)是一个可调用的对象,如函数指针、成员函数指针或者函数对象,二、共享状态(shared state),用于保存任务的返回值,可以通过 std::future 对象来达到异步访问共享状态的效果。

可以通过 std::packged_task::get_future 来获取与共享状态相关联的 std::future 对象。在调用该函数之后,两个对象共享相同的共享状态,具体解释如下:

  • std::packaged_task 对象是异步 Provider,它在某一时刻通过调用被包装的任务来设置共享状态的值。

  • std::future 对象是一个异步返回对象,通过它可以获得共享状态的值,当然在必要的时候需要等待共享状态标志变为 ready.

std::packaged_task 的共享状态的生命周期一直持续到最后一个与之相关联的对象被释放或者销毁为止。下面一个小例子大致讲了 std::packaged_task 的用法:

复制代码

#include <iostream>     // std::cout
#include <future>       // std::packaged_task, std::future
#include <chrono>       // std::chrono::seconds
#include <thread>       // std::thread, std::this_thread::sleep_for

// count down taking a second for each value:
int countdown (int from, int to) {
    for (int i=from; i!=to; --i) {
        std::cout << i << '\n';
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    std::cout << "Finished!\n";
    return from - to;
}

int main ()
{
    std::packaged_task<int(int,int)> task(countdown); // 设置 packaged_task
    std::future<int> ret = task.get_future(); // 获得与 packaged_task 共享状态相关联的 future 对象.

    std::thread th(std::move(task), 10, 0);   //创建一个新线程完成计数任务.

    int value = ret.get();                    // 等待任务完成并获取结果.

    std::cout << "The countdown lasted for " << value << " seconds.\n";

    th.join();
    return 0;
}

复制代码

执行结果为:

复制代码

concurrency ) ./Packaged_Task1 
10
9
8
7
6
5
4
3
2
1
Finished!
The countdown lasted for 10 seconds.

复制代码

std::packaged_task 构造函数

default (1)
packaged_task() noexcept;
initialization (2)
template <class Fn>
  explicit packaged_task (Fn&& fn);
with allocator (3)
template <class Fn, class Alloc>
  explicit packaged_task (allocator_arg_t aa, const Alloc& alloc, Fn&& fn);
copy [deleted] (4)
packaged_task (const packaged_task&) = delete;
move (5)
packaged_task (packaged_task&& x) noexcept;

std::packaged_task 构造函数共有 5 中形式,不过拷贝构造已经被禁用了。下面简单地介绍一下上述几种构造函数的语义:

  1. 默认构造函数,初始化一个空的共享状态,并且该 packaged_task 对象无包装任务。
  2. 初始化一个共享状态,并且被包装任务由参数 fn 指定。
  3. 带自定义内存分配器的构造函数,与默认构造函数类似,但是使用自定义分配器来分配共享状态。
  4. 拷贝构造函数,被禁用。
  5. 移动构造函数。

下面例子介绍了各类构造函数的用法:

复制代码

#include <iostream>     // std::cout
#include <utility>      // std::move
#include <future>       // std::packaged_task, std::future
#include <thread>       // std::thread

int main ()
{
    std::packaged_task<int(int)> foo; // 默认构造函数.

    // 使用 lambda 表达式初始化一个 packaged_task 对象.
    std::packaged_task<int(int)> bar([](int x){return x*2;});

    foo = std::move(bar); // move-赋值操作,也是 C++11 中的新特性.

    // 获取与 packaged_task 共享状态相关联的 future 对象.
    std::future<int> ret = foo.get_future();

    std::thread(std::move(foo), 10).detach(); // 产生线程,调用被包装的任务.

    int value = ret.get(); // 等待任务完成并获取结果.
    std::cout << "The double of 10 is " << value << ".\n";

return 0;
}

复制代码

与 std::promise 类似, std::packaged_task 也禁用了普通的赋值操作运算,只允许 move 赋值运算。

std::packaged_task::valid 介绍

检查当前 packaged_task 是否和一个有效的共享状态相关联,对于由默认构造函数生成的 packaged_task 对象,该函数返回 false,除非中间进行了 move 赋值操作或者 swap 操作。

请看下例:

复制代码

#include <iostream>     // std::cout
#include <utility>      // std::move
#include <future>       // std::packaged_task, std::future
#include <thread>       // std::thread

// 在新线程中启动一个 int(int) packaged_task.
std::future<int> launcher(std::packaged_task<int(int)>& tsk, int arg)
{
    if (tsk.valid()) {
        std::future<int> ret = tsk.get_future();
        std::thread (std::move(tsk),arg).detach();
        return ret;
    }
    else return std::future<int>();
}

int main ()
{
    std::packaged_task<int(int)> tsk([](int x){return x*2;});

    std::future<int> fut = launcher(tsk,25);

    std::cout << "The double of 25 is " << fut.get() << ".\n";

    return 0;
}

复制代码

std::packaged_task::get_future 介绍

返回一个与 packaged_task 对象共享状态相关的 future 对象。返回的 future 对象可以获得由另外一个线程在该 packaged_task 对象的共享状态上设置的某个值或者异常。

请看例子(其实前面已经讲了 get_future 的例子):

复制代码

#include <iostream>     // std::cout
#include <utility>      // std::move
#include <future>       // std::packaged_task, std::future
#include <thread>       // std::thread

int main ()
{
    std::packaged_task<int(int)> tsk([](int x) { return x * 3; })); // package task

    std::future<int> fut = tsk.get_future();   // 获取 future 对象.

    std::thread(std::move(tsk), 100).detach();   // 生成新线程并调用packaged_task.

    int value = fut.get();                     // 等待任务完成, 并获取结果.

    std::cout << "The triple of 100 is " << value << ".\n";

    return 0;
}

复制代码

std::packaged_task::operator()(Args... args) 介绍

调用该 packaged_task 对象所包装的对象(通常为函数指针,函数对象,lambda 表达式等),传入的参数为 args. 调用该函数一般会发生两种情况:

  • 如果成功调用 packaged_task 所包装的对象,则返回值(如果被包装的对象有返回值的话)被保存在 packaged_task 的共享状态中。
  • 如果调用 packaged_task 所包装的对象失败,并且抛出了异常,则异常也会被保存在 packaged_task 的共享状态中。

以上两种情况都使共享状态的标志变为 ready,因此其他等待该共享状态的线程可以获取共享状态的值或者异常并继续执行下去。

共享状态的值可以通过在 future 对象(由 get_future获得)上调用 get 来获得。

由于被包装的任务在 packaged_task 构造时指定,因此调用 operator() 的效果由 packaged_task 对象构造时所指定的可调用对象来决定:

  • 如果被包装的任务是函数指针或者函数对象,调用 std::packaged_task::operator() 只是将参数传递给被包装的对象。
  • 如果被包装的任务是指向类的非静态成员函数的指针,那么 std::packaged_task::operator() 的第一个参数应该指定为成员函数被调用的那个对象,剩余的参数作为该成员函数的参数。
  • 如果被包装的任务是指向类的非静态成员变量,那么 std::packaged_task::operator() 只允许单个参数。

std::packaged_task::make_ready_at_thread_exit 介绍

该函数会调用被包装的任务,并向任务传递参数,类似 std::packaged_task 的 operator() 成员函数。但是与 operator() 函数不同的是,make_ready_at_thread_exit 并不会立即设置共享状态的标志为 ready,而是在线程退出时设置共享状态的标志。

如果与该 packaged_task 共享状态相关联的 future 对象在 future::get 处等待,则当前的 future::get 调用会被阻塞,直到线程退出。而一旦线程退出,future::get 调用继续执行,或者抛出异常。

注意,该函数已经设置了 promise 共享状态的值,如果在线程结束之前有其他设置或者修改共享状态的值的操作,则会抛出 future_error( promise_already_satisfied )。

std::packaged_task::reset() 介绍

重置 packaged_task 的共享状态,但是保留之前的被包装的任务。请看例子,该例子中,packaged_task 被重用了多次:

复制代码

#include <iostream>     // std::cout
#include <utility>      // std::move
#include <future>       // std::packaged_task, std::future
#include <thread>       // std::thread

// a simple task:
int triple (int x) { return x*3; }

int main ()
{
    std::packaged_task<int(int)> tsk (triple); // package task


    std::future<int> fut = tsk.get_future();
    std::thread (std::move(tsk), 100).detach();
    std::cout << "The triple of 100 is " << fut.get() << ".\n";


    // re-use same task object:
    tsk.reset();
    fut = tsk.get_future();
    std::thread(std::move(tsk), 200).detach();
    std::cout << "Thre triple of 200 is " << fut.get() << ".\n";

    return 0;
}

复制代码

std::packaged_task::swap() 介绍

交换 packaged_task 的共享状态。

好了,std::packaged_task 介绍到这里,本文参考了 http://www.cplusplus.com/reference/future/packaged_task/ 相关的内容。后一篇文章我将向大家介绍 std::future,std::shared_future 以及 std::future_error,另外还会介绍 <future> 头文件中的 std::async,std::future_category 函数以及相关枚举类型。

猜你喜欢

转载自blog.csdn.net/lusic01/article/details/82969275