1. 程式人生 > >C++學習筆記--多執行緒

C++學習筆記--多執行緒

執行緒與程序優缺點對比:
多執行緒開銷小,但難於管理,且不能用於分散式系統;
多程序開銷大,作業系統會進行一部分管理,因此使用者管理就比較簡單,可用於分散式;
通常多執行緒和多程序結合使用。
參考資料:http://edu.csdn.net/course/detail/2303/35894?auto_start=1
程式碼例項:

1 最簡單的多執行緒

#include <iostream>
#include <thread>

void function_1()
{
    std::cout <<"www.oxox.work"<<std::endl;
}

int
main() //主執行緒 { std::thread t1(function_1); //建立並初始化一個執行緒,且執行緒t1建立完之後就開始執行 //t1.detach(); //主執行緒和t1執行緒互不影響,detach可以使主執行緒不等待t1執行緒結束即可執行,這樣會使得程式的執行結果沒有內容輸出,因為主執行緒還沒有等到t1輸出內容就結束了。執行緒被deatch之後就不能再join了,如果再join,編譯不會報錯,但是執行會報錯 if(t1.joinable()) //如果執行了t1.detach(),t1.joinable()為false { t1.join(); //主執行緒將等待t1執行緒結束後再執行
} return 0; }

2 主執行緒和子執行緒交叉執行

#include <iostream>
#include <thread>

using namespace std;
void function_1()
{
    cout <<"www.oxox.work"<<endl;
}

class Factor
{
public:
    void operator()()
    {
        for(int i = 0; i > -100; --i)
        {
            cout
<< "from t1: " << i << endl; } } }; int main() //主執行緒 { //std::thread t1(function_1); //使用函式建立並初始化一個執行緒,且執行緒開始執行 Factor fct; std::thread t1(fct); //使用函式物件建立並初始化一個執行緒 try { for(int i = 0; i < 100; ++i) { cout << "from main: " << i << endl; } } catch(...) //上面的for迴圈屬於主執行緒,如果上面丟擲異常,但是沒有try catch,主執行緒終止,t1執行緒也終止了,這樣是非執行緒安全的。新增try catch之後,即使主執行緒異常,t1執行緒也能正常執行結束 { t1.join(); //主執行緒將等待t1執行緒結束後再執行 throw; } return 0; }

3 主執行緒和子執行緒之間實現記憶體共享

#include <iostream>
#include <thread>
#include <string>

using namespace std;
void function_1()
{
    cout <<"www.oxox.work"<<endl;
}

class Factor
{
public:
    void operator()(string &s)
    {
        cout << "from t1: " << s << endl;
        s = "I love XuHuanDaXue";
    }
};

int main()  //主執行緒
{
    string s("I love www.oxox.work");    //string變數s被主執行緒和t1執行緒使用,可通過s實現記憶體共享
    Factor fct;
    //std::thread t1(fct, s);    //這種方式並不能在t1執行緒中改變s,因為s將被拷貝
        std::thread t1(fct, std::ref(s));   //t1執行緒可改變s,因為引數是s的引用
    t1.join();
    cout << "from main: " << s.c_str() << endl;    //主執行緒使用了被t1執行緒改變的s
    return 0;
}

4 執行緒移動與執行緒ID

#include <iostream>
#include <thread>
#include <string>

using namespace std;
void function_1()
{
    cout <<"www.oxox.work"<<endl;
}

class Factor
{
public:
    void operator()(string &s)
    {
        cout << "from t1: " << s << endl;
        s = "I love XuHuanDaXue";
    }
};

int main()  //主執行緒
{
    string s("I love www.oxox.work");
    Factor fct;
    cout << std::this_thread::get_id() << endl; //獲取主執行緒ID,每個執行緒都有個ID
    std::thread t1(fct, std::move(s));  //此處s被移動,移動操作比拷貝要高效,比引用要安全
    std::thread t2=std::move(t1);   //執行緒物件只能被移動,但不能被拷貝,所以必須使用std::move()
    cout << t2.get_id() << endl;    //獲取t2執行緒ID
    //t1.join();
    t2.join();  //t1被移動到t2,t1已經是空的了,所以得使用t2.join()

    cout << "from main: " << s.c_str() << endl;
    cout << std::thread::hardware_concurrency() <<endl; //檢測CPU能支援的最大執行緒數,如果使用者建立的執行緒數超過了CPU能支援的,反而會引起效能下降
    return 0;
}

5 執行緒安全

下面的程式碼是非執行緒安全的,主執行緒和t1執行緒將競爭資源cout,只要競爭到資源就隨時可以將內容寫入到輸出流cout,使得輸出看起來是下面這樣的:

from t1: 0
from t1: -1
from t1: -2
from main: 0
from main: 1
from main: 2
from main: 3
from main: 4
from main: 5
from main: 6
from main: 7
from t1: -3
from t1: -4
from t1: -5

#include <iostream>
#include <thread>
#include <string>

using namespace std;

void function_1()
{
    for(int i = 0; i > -100; --i)
    {
        cout << "from t1: " << i << endl;
    }
}

int main()  //主執行緒
{
    std::thread t1(function_1);
    for(int i = 0; i < 100; ++i)
    {
        cout << "from main: " << i << endl;
    }
    t1.join();  //主執行緒將等待t1執行緒結束後再執行
    return 0;
}

可以加mutex鎖,有執行緒正在使用cout,其他執行緒就不能使用,這樣cout在當前程式中是執行緒安全的,能使得輸出是有序的,是下面這樣的:

from main: 0
from t1: 0
from main: 1
from t1: -1
from main: 2
from t1: -2
from main: 3
from t1: -3
from main: 4
from t1: -4
from main: 5
from t1: -5

#include <iostream>
#include <thread>
#include <string>
#include <mutex>

using namespace std;

std::mutex mtx;
void shared_print(string s, int id)
{
    mtx.lock();
    cout <<  s.c_str() << id << endl;
    mtx.unlock();
}

void function_1()
{
    for(int i = 0; i > -100; --i)
    {
        shared_print("from t1: ",i);
    }
}

int main()  //主執行緒
{
    std::thread t1(function_1);
    for(int i = 0; i < 100; ++i)
    {
                shared_print("from main: ",i);
    }
    t1.join();  //主執行緒將等待t1執行緒結束後再執行
    return 0;
}

上面的程式碼中,當shared_print函式中cout那一行丟擲異常時,mtx.unclock()不會被執行,mtx將被永遠地鎖住。這時可以使用std::lock_guard來保證mtx會被解鎖。shared_print函式修改如下:

void shared_print(string s, int id)
{
   std::lock_guard<std::mutex> locker(mtx);    //lock_guard物件建立時會自動對mtx加鎖,離開作用域被析構時,mtx會被自動解鎖,這樣即使cout這行發生異常,mtx也能被解鎖了 
   cout <<  s.c_str() << id << endl;
}

但是上面的程式碼仍然不是安全的,因為cout是個全域性變數,並沒有完全在mtx的保護下,其他執行緒仍然可以在不加鎖的情況下使用cout。為了完整地保護資源,必須使資源和互斥物件進行繫結。程式碼如下:

#include <iostream>
#include <thread>
#include <string>
#include <mutex>
#include <fstream>

using namespace std;

class LofFile
{
public:
    LofFile()
    {
        f.open("log.txt");
    }
    void shared_print(string s, int id)
    {
        std::lock_guard<std::mutex> locker(m_mutex); 
        f << s << id << endl;
    }
private:
    std::mutex m_mutex;
    std::ofstream f;
};

void function_1(LofFile& log)
{
    for(int i = 0; i > -100; --i)
    {
        log.shared_print("from t1: ",i);
    }
}

int main()  //主執行緒
{
       LofFile log; 
       std::thread t1(function_1, std::ref(log));
    for(int i = 0; i < 100; ++i)
    {
                log.shared_print("from main: ",i);
    }
    t1.join();  //主執行緒將等待t1執行緒結束後再執行
    return 0;
}

上面的程式碼將資源std::ofstream f和互斥物件std::mutex m_mutex定義在LogFile類中,類外的執行緒不可訪問資源f,使用類物件的shared_print函式的執行緒也能保證資源f必定有一個互斥物件m_mutex來保護。簡單地講,就是資源和互斥物件必定成對地出現在同一個作用域中,因此資源一定會受互斥物件保護。注意,這裡的程式碼使用了資源std::ofstream f,而不是cout,是因為cout是全域性的資源。

6 避免死鎖

死鎖是指兩個執行緒互相鎖住,互相等待釋放,卻不能釋放,下面的程式碼發生了死鎖,程式的輸出可能是這樣的(程式被暫停在某處,並沒有成功執行完畢):

from t1: 0
from main: 0
from t1: -1
from main: 1

#include <iostream>
#include <thread>
#include <string>
#include <mutex>
#include <fstream>

using namespace std;

class LofFile
{
public:
    LofFile()
    {
        f.open("log.txt");
    }
    void shared_print(string s, int id)    //函式被t1執行緒呼叫
    {
        std::lock_guard<std::mutex> locker(m_mutex); 
        std::lock_guard<std::mutex> locker2(m_mutex2); 
        cout << s << id << endl;    //為了更直觀得看到結果,這裡改成cout
    }
    void shared_print2(string s, int id)    //函式被主執行緒呼叫
    {
        std::lock_guard<std::mutex> locker2(m_mutex2); 
        std::lock_guard<std::mutex> locker(m_mutex); 
        cout << s << id << endl;    //為了更直觀得看到結果,這裡改成cout
    }
    //如果t1執行緒執行到鎖住m_mutex時,主執行緒正好執行到鎖住m_mutex2,t1執行緒繼續執行下一語句發現m_mutex2被鎖住了,於是等待m_mutex2被解鎖,而主執行緒也繼續執行下一語句發現m_mutex被鎖住了,於是等待m_mutex被解鎖,兩個執行緒相互等待,這樣就發生了死鎖,程式就一直暫停在哪裡
private:
    std::mutex m_mutex;
    std::mutex m_mutex2;
    std::ofstream f;
};

void function_1(LofFile& log)
{
    for(int i = 0; i > -100; --i)
    {
        log.shared_print("from t1: ",i);
    }
}

int main()  //主執行緒
{
       LofFile log; 
       std::thread t1(function_1, std::ref(log));
    for(int i = 0; i < 100; ++i)
    {
                log.shared_print2("from main: ",i);
    }
    t1.join();  //主執行緒將等待t1執行緒結束後再執行
    return 0;
}

上面的程式碼中,m_mutex和m_mutex2在兩個執行緒中加鎖的順序是相反的,如果將語句的順序改成一致就不會發生死鎖。在C++標準庫中提供了std::lock,是規範的處理死鎖問題的方法,把上面的兩個函式改成下面這樣:

void shared_print(string s, int id)    //函式被t1執行緒呼叫
{
    std::lock(m_mutex, m_mutex2);   //std::lock可以指定鎖的順序,引數為lock1,lock2,...,lockn,它的引數個數是不固定的,有多少個鎖就可以使用多少個引數
    std::lock_guard<std::mutex> locker(m_mutex, std::adopt_lock);    //這裡新增std::adopt_lock是告知locker,m_mutex已經被鎖住,locker要做的只是獲得m_mutex的所有權,然後在析構時將其解鎖即可
    std::lock_guard<std::mutex> locker2(m_mutex2, std::adopt_lock); 
    cout << s << id << endl;    //為了更直觀得看到結果,這裡改成cout
}
void shared_print2(string s, int id)    //函式被主執行緒呼叫
{
    std::lock(m_mutex, m_mutex2);
    std::lock_guard<std::mutex> locker2(m_mutex2, std::adopt_lock); 
    std::lock_guard<std::mutex> locker(m_mutex, std::adopt_lock); 
    cout << s << id << endl;    //為了更直觀得看到結果,這裡改成cout
}

上面的程式碼中,即使locker和locker2的順序是相反的,但是m_mutex和m_mutex2加鎖的順序是相同的,因為std::lock指定了加鎖的順序。
為了避免程式設計中出現死鎖,可以遵循以下幾條規則:
(1)使用一個mutex即可滿足要求的場合,絕不使用兩個mutex;
(2)如果某個作用域中已經使用了一個mutex,那麼要小心該作用域中的函式呼叫,因為該函式呼叫中可能包括其他mutex;
(3)無法避免地需要使用兩個以上mutex時,儘量使用std::lock指定鎖的順序,但是在某些極端情況下std::lock無法使用,就要小心地保證加鎖的語句順序。

7 Unique Lock和call_once

#include <iostream>
#include <thread>
#include <string>
#include <mutex>
#include <fstream>

using namespace std;

class LogFile
{
public:
    LogFile()
    {
        f.open("log.txt");
    }
    void shared_print(string s, int id)
    {
        //std::lock_guard<std::mutex> locker(m_mutex);   //lock_guard物件在構造時自動m_mutex加鎖,在析構時自動對m_mutex解鎖,使用者無法自由控制何時加鎖解鎖
        //std::unique_lock<std::mutex> locker(m_mutex);   //unique_lock可以起到與lock_guard一樣的功能,預設情況下是構造自動加鎖,析構自動解鎖
        std::unique_lock<std::mutex> locker(m_mutex, std::defer_lock);  //還可以使用defer_lock告知locker,不要在構造時自動加鎖m_mutex
        locker.lock();   //然後使用者自行給m_mutex加鎖   
        f << s << id << endl;
        locker.unlock();    //還可以在需要的時候自行解鎖,這樣m_mutex只鎖住了上面一行語句,之後的操作就沒有被m_mutex鎖住
        //程式碼塊...
        locker.lock();    //接下來還可以隨意地自行加鎖和解鎖
        //程式碼塊...
        locker.unlock();
        //lock_guard和unique_lock都不能被複制,但是unique_lock可被移動。當unique_lock被移動時,m_mutex的控制權也從一個unique_lock轉移到另一個unique_lock。另外,unique_lock的代價比lock_guard高,所以使用lock_guard即可滿足要求的場合就使用lock_guard
        //std::unique_lock<std::mutex> locker2 = std::move(locker);    
    }
private:
    std::mutex m_mutex;
    std::ofstream f;
};

void function_1(LogFile& log)
{
    for(int i = 0; i > -100; --i)
    {
        log.shared_print("from t1: ",i);
    }
}

int main()  //主執行緒
{
       LogFile log; 
       std::thread t1(function_1, std::ref(log));
    for(int i = 0; i < 100; ++i)
    {
                log.shared_print("from main: ",i);
    }
    t1.join();  //主執行緒將等待t1執行緒結束後再執行
    return 0;
}

上面使用的所有示例程式碼中,LogFile類都是在構建函式中開啟log.txt檔案,如果我們想只在呼叫shared_print函式的時候才打開檔案,可以做如下修改:

class LogFile
{
public:
    LogFile()
    {
        //f.open("log.txt");
    }
    void shared_print(string s, int id)
    {
        //if(!f.is_open())    //這段程式碼不是執行緒安全的,因為兩個執行緒可能同時執行到f.open處,兩次開啟檔案,將會執行報錯
        //{
        //    f.open("log.txt");
        //}

        //if(!f.is_open())    //這段程式碼仍然不是執行緒安全的,假設a執行緒尚未執行完f.open,b執行緒正好檢測到檔案未開啟,然後發現m_mutex_fopen被鎖住,於是等待,接著a執行緒打開了檔案,解鎖m_mutex_fopen,此時b執行緒發現解鎖了,立即執行f.open,這樣就兩次開啟檔案,將會執行報錯
        //{
        //    std::unique_lock<std::mutex> locker(m_mutex_fopen, std::defer_lock);
        //    f.open("log.txt");
        //}

        //{   //這段程式碼是執行緒安全的,但是這存在一個性能上的問題,即每次函式呼叫都要對m_mutex_fopen進行加鎖和解鎖,這會無意義地消耗計算機資源
        //    std::unique_lock<std::mutex> locker(m_mutex_fopen, std::defer_lock);
        //    if(!f.is_open())  f.open("log.txt");
        //}
        std::call_once(m_flag, [&](){f.open("log.txt")})    //這行程式碼能確保後面的lambda函式只被一個執行緒呼叫一次,是C++標準庫的推薦用法
        std::unique_lock<std::mutex> locker(m_mutex, std::defer_lock);  
        f << s << id << endl;  
    }
private:
    std::mutex m_mutex;
    std::mutex m_mutex_fopen;
    std::once_flag m_flag;
    std::ofstream f;
};

8 條件變數

條件變數適用於a執行緒需要等待b執行緒觸發某種條件,a執行緒才能執行的場合

#include <iostream>
#include <thread>
#include <string>
#include <mutex>
#include <fstream>
#include <deque>
#include <functional>
#include <condition_variable>

using namespace std;
std::deque<int> q;
std::mutex mu;
std::condition_variable cond;

void function_1()
{
    int count = 10;
    while (count > 0)
    {
        std::unique_lock<mutex> locker(mu);
        q.push_front(count);
        locker.unlock();
        cond.notify_one();    //啟用條件變數cond
        cond.notify_all();    //notify_one只能啟用一個正在等待cond被啟用的執行緒
                                     //notify_all可以啟用所有正在等待cond被啟用的執行緒
        std::this_thread::sleep_for(chrono::seconds(1));
        count--;
    }
}

void function_2()
{
    int data = 0;
    while (data != 1)
    {
        std::unique_lock<mutex> locker(mu);
        /*if (!q.empty())
        {
            data = q.back();
            q.pop_back();
            locker.unlock();
            cout << "t2 got a value from t1: " << data << endl;
        }
        else    //當q為空時,一直在執行while迴圈,不斷查詢,直到q非空,這樣是很低效的,應該使用條件變數
        {
            locker.unlock();
        }*/

        //等待執行緒1呼叫notify_one()啟用條件變數bond,只有cond被啟用,才可以
        //執行後面的語句。此處locker作為引數傳遞給wait之前已經加鎖了,一個
        //執行緒不會在鎖住的情況下休眠,所以wait()先將mu解鎖,使其休眠,然後
        //又加鎖。由於需要重複加解鎖,所以此處得用unique_lock,而不能使用
        //lock_guard
        //某些情況下,執行緒可能被自己啟用,這稱為偽啟用,給條件變數新增一個
        //lambda函式作為增加的一個條件,滿足條件才可以被啟用
        cond.wait(locker, [](){return !q.empty(); });
        data = q.back();
        q.pop_back();
        locker.unlock();
        cout << "t2 got a value from t1: " << data << endl;
    }
}

int main()
{
    std::thread t1(function_1);
    std::thread t2(function_2);
    t1.join();
    t2.join();
    return 0;
}

9 Future, Promise和async()

std::future類可以從子執行緒獲取返回值,然後在父執行緒中使用

#include <iostream>
#include <thread>
#include <string>
#include <mutex>
#include <fstream>
#include <future>

using namespace std;

int factorial(int N)
{
    int res = 1;
    for (int i = N; i > 1; --i)
    {
        res *= i;
    }
    cout << "Result is: "<< res << endl;
    return res;
}

int main()
{
    int x;
    //std::thread t1(factorial,4);   
    //t1.join();
    //有時希望將子執行緒的執行結果返回給父執行緒,可以修改上面的程式碼將x的引用
    //傳給t1,通過記憶體共享的方式來實現,但是更簡單的方式是使用future物件,
    //取名future的含義是,這個物件可以從未來獲取某個值,即等待未來子執行緒執
    //行結束時返回的值。實際上async並不一定會建立子執行緒,如果明確指定第一
    //個引數為std::launch::deferred,將不建立子執行緒,此時factorial()呼叫將被延期,
    //等到get()執行後,就在父執行緒中呼叫factorial。如果引數為std::launch::async,
    //就是明確指定建立子執行緒。async的引數預設是std::launch::async | std::launch::deferred,
    //意思是是否建立子執行緒將取決於實現(沒有弄明白取決於什麼實現?)
    //std::future<int> fu = std::async(factorial, 4);
    std::future<int> fu = std::async(std::launch::async | std::launch::deferred, factorial, 4);
    x = fu.get();    //get將會等待子執行緒結束,並取回子執行緒返回的結果,get只能被呼叫一次,呼叫兩次程式會執行報錯
    return 0;
}

std::promise可以從父執行緒獲取值到子執行緒中使用

#include <iostream>
#include <thread>
#include <string>
#include <mutex>
#include <fstream>
#include <future>

using namespace std;

int factorial(std::future<int>& f)
{
    int N = f.get();
    int res = 1;
    for (int i = N; i > 1; --i)
    {
        res *= i;
    }
    cout << "Result is: "<< res << endl;
    return res;
}

int main()
{
    int x;
    std::promise<int> p;
    std::future<int> f = p.get_future();
    std::future<int> fu = std::async(std::launch::async, factorial, std::ref(f));
    p.set_value(4);    //這裡必須進行set_value,否則在factorial函式的int N = f.get()這行會丟擲std::future_error::broken_promise的異常
    x = fu.get();
    cout << "Get from child: " << x << endl;
    return 0;
}

如果需要建立多個都呼叫factorial執行緒,每個執行緒都需要一個f引數,但是std::future不能被拷貝,此時可以使用std::shared_future,它可以被拷貝,可將int factorial(std::future& f)改為int factorial(std::shared_future f),同時main函式做如下修改:

int main()
{
    int x;
    std::promise<int> p;
    std::future<int> f = p.get_future();
    std::shared_future<int> sf = f.share();
    std::future<int> fu = std::async(std::launch::async, factorial, sf);
    std::future<int> fu2 = std::async(std::launch::async, factorial, sf);
    std::future<int> fu3 = std::async(std::launch::async, factorial, sf);   
    p.set_value(4);    
    return 0;
}

10 建立執行緒的不同方式

#include <iostream>
#include <thread>
#include <string>
#include <mutex>
#include <fstream>
#include <future>

using namespace std;

class A
{
public:
    void f(int x, char c){}
    int operator()(int N){return 0;}
};

void foo(int x){}

int main()
{
    A a;
    std::thread t1(a, 6);    //傳遞a的拷貝給子執行緒
    std::thread t2(std::ref(a), 6);    //傳遞a的引用給子執行緒
    std::thread t3(std::move(a), 6);    //從主執行緒移動a到子執行緒,a在主執行緒中不再有效
    std::thread t4(A(), 6);   //傳遞臨時建立的a物件給子執行緒
    std::thread t5(foo, 6);   //傳遞自定義的函式給子執行緒
    std::thread t6([](int x){return x*x;}, 6);   //傳遞lambda函式給子執行緒
    std::thread t7(&A::f, a, 8, 'w');   //傳遞a的拷貝的成員函式f給子執行緒
    std::thread t8(&A::f, &a, 8, 'w');   //傳遞a的地址的成員函式f給子執行緒
    std::async(std::launch::async, a, 6);    //上面的八種方式同樣適用於async
    return 0;
}

11 Packaged_task

#include <iostream>
#include <thread>
#include <string>
#include <mutex>
#include <fstream>
#include <future>
#include <deque>

using namespace std;

int factorial(int N)
{
    int res = 1;
    for (int i = N; i > 1; --i)
    {
        res *= i;
    }
    cout << "Result is: "<< res << endl;
    return res;
}

std::deque<std::packaged_task<int()> > task_q;
std::mutex mu;
std::condition_variable cond;
void thread_1()
{
    std::packged_task<int()> t;
    {
       std::unique_lock<std::mutex> locker(mu);
       cond.wait(locker, []{return !task_q.empty();});
       t = std::move(task_q.front());
    }
    t();
}

int main()
{
    std::thread t1(thread_1);
    std::packaged_task<int()> t(std::bind(factorial, 6));   //packaged_task只能傳遞一個引數,如果還要傳遞引數6,可以使用bind函式
    std::future<int> ret = t.get_future();    //獲得與packaged_task共享狀態相關聯的future物件
    {
       std::unique_lock<std::mutex> locker(mu);
       task_q.push_back(std::move(t));
    }    
    cond.notify_one();
    int value = ret.get();    //等待任務完成並獲取結果
    t1.join()

    //這兩句可以實現和packaged_task類似的功能,但是packaged_task特點是可以將一個可呼叫物件關聯到一個future變數,然後非同步獲取可呼叫物件的返回結果
    //auto t = std::bind(factorial, 6);  
    //t();

    //std::future<int> fu = std::async(factorial, 4);
    x = fu.get();

    std::packaged_task<int(int)> t(factorial);   //以一個可呼叫物件為引數,並且可以非同步獲取該呼叫物件的返回結果
    std::future<int> ret = t.get_future();    //獲得與packaged_task共享狀態相關聯的future物件
    int value = ret.get();    //等待任務完成並獲取結果  
}

12 時間約束

可以讓某個類物件休眠一段時間,或者到達指定的時間點才停止休眠

#include <iostream>
#include <thread>
#include <string>
#include <mutex>
#include <fstream>
#include <future>
#include <deque>

using namespace std;

int factorial(int N)
{
    int res = 1;
    for (int i = N; i > 1; --i)
    {
        res *= i;
    }
    cout << "Result is: " << res << endl;
    return res;
}

int main()
{
    std::thread t1(factorial, 6);
    std::this_thread::sleep_for(chrono::milliseconds(3));    //執行緒休眠3毫秒
    chrono::steady_clock::time_point tp = chrono::steady_clock::now() + chrono::milliseconds(3); //一個靜態的時間點
    std::this_thread::sleep_until(tp);    //一直休眠,直到達到指定的時間點tp,才結束休眠

    std::mutex mu;
    std::unique_lock<std::mutex> locker(mu);
    locker.try_lock_for(chrono::milliseconds(3));
    locker.try_lock_until(tp);

    std::condition_variable cond;
    cond.wait_for(locker, chrono::milliseconds(3));
    cond.wait_until(locker, tp);

    std::promise<int> p;
    std::future<int> f = p.get_future();
    f.wait_for(chrono::milliseconds(3));
    f.wait_until(tp);
}