c++11 promise和future

使用promise,編譯時需要 -std=c++11 -pthread 才會動。

就像其他語言一樣,promise主要用來做異步流程控制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <future>
#include <thread>
#include <iostream>

using namespace std;

void runner(promise<int> *p) {
    cout << "do some io task" << endl;
    p->set_value(3);
}

int main() {
    promise<int> p;
    std::thread t(runner, &p);

    int ret = p.get_future().get(); // Wait p.set_value()
    cout << ret << endl;

    t.join();

    return 0;
}
do some io task
3

比較常使用到promise的狀況是跟其他library接的時候,對方透過callback或listener,

將執行完成的結果回傳,這時候就可以包裝變成同步的寫法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <future>
#include <thread>
#include <string>
#include <exception>
#include <iostream>

using namespace std;

// Someone provides Downloader class.
class Downloader {
    public:
        void SetCallback(function<void(int,string)> cb) { cb_ = cb; }
        void Download(string url) {
            thread([=] () {
                    cout << "downloading: " << url << endl;
                    cb_(0, "ok");
            }).detach();
        }
    private:
        function<void(int,string)> cb_;
};

void download_callback(promise<string> *p, int status, string data) {
    if(status != 0) {
        p->set_exception(make_exception_ptr(runtime_error("download failed")));
    } else {
        p->set_value(data);
    }
}

int main() {
    promise<string> p;

    Downloader d;
    d.SetCallback(bind(download_callback, &p,
        placeholders::_1, placeholders::_2));
    d.Download("127.0.0.1");

    try {
        string ret = p.get_future().get();
        cout << ret << endl;
    } catch(exception &e) {
        cout << "future: " << e.what() << endl;
    }

    return 0;
}
downloading: 127.0.0.1
ok

promise也有一些限制:它只能被使用一次,每次使用後就要宣告新的promise object。

如果使用condition variable的話就可以重複使用 wait()notify_one()

但是必須自己處理value的回傳。換句話說,每次呼叫function都需要一個獨立的promise object。

如果上面的Download()連續呼叫兩次,程式就會發生錯誤。

所以Downloader內部還需要一個mutex防止被重複呼叫,才能正確運作。

一個比較好的寫法是從Downloader直接提供future物件給caller等待。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <future>
#include <unistd.h>
#include <thread>
#include <string>
#include <exception>
#include <iostream>
#include <chrono>

using namespace std;

class Downloader {
    public:
        future<string> Download(string url) {
            auto p = make_shared<promise<string>>();
            thread([=] () {
                    cout << "downloading: " << url << endl;
                    sleep(1);
                    download_callback(p.get(), 0, "ok");
            }).detach();
            return p->get_future();
        }
    private:
        void download_callback(promise<string> *p, int status, string data) {
            if(status != 0) {
                p->set_exception(make_exception_ptr(runtime_error("download failed")));
            } else {
                p->set_value(data);
            }
        }
};


int main() {
    Downloader d;

    try {
        auto now = time(NULL);
        auto d1 = d.Download("127.0.0.1");
        auto d2 = d.Download("127.0.0.2");
        auto d3 = d.Download("127.0.0.3");
        cout << d1.get() << endl;
        cout << d2.get() << endl;
        cout << d3.get() << endl;
        cout << time(NULL) - now << endl;
    } catch(exception &e) {
        cout << "future: " << e.what() << endl;
    }

    return 0;
}
downloading: 127.0.0.3
downloading: 127.0.0.2
downloading: 127.0.0.1
ok
ok
ok
1