[c++] c++異步處理和clock函數踩坑

c++異步處理和clock函數踩坑


最近在c++異步處理的地方踩了個坑,提前劇透一下,坑來自clock()計時器函數。

1. 異步處理

異步處理,在本文,只是爲了多線程加速程序運行速度。

異步處理一般需要有如下步驟,

1.1 確認或者封裝需要異步處理的函數

這個過程可以理解爲,將一個運行速度慢,需要加速的部分,拆解爲很多小模塊,每個小模塊自身比較快。那麼,在每個線程中,只運行這個小塊,多個線程同時工作的時候,總體的時間就會比較少。

1.2 調用異步處理函數std::async()

多線程有很多中方法,windows下的多線程有CreateThread方法,c++11裏面也包含了std::thread()方法,std::async()方法,這裏只介紹std::async()。原因是,筆者認爲多線程並行加速的時候,這個方法將底層很多處理都封裝了,用起來簡單、便捷。

#include <iostream>
#include <future>
#include <ctime>
#include <thread>

using namespace std;

bool pause_thread(int n) {
    double res = 0.;
    for (double i = 0; i < 10.e9; i += 1.0) {
        res += i * i;
    }
    return true;
}
void func1() {
    future<bool> f1 = async(launch::async, pause_thread, 3);
    future<bool> f2 = async(launch::async, pause_thread, 2);
    future<bool> f3 = async(launch::async, pause_thread, 1);
    f1.wait();
    f2.wait();
    f3.wait();
}
int main() {
    cout << "---------------------func1: multi-thread" << endl;
    func1();
}

啓動線程:當std::async()函數被調用的時候,啓動了一個新的線程。上面的代碼中,啓動了三個線程。這三個線程分別在不同的cpu核心執行自身的任務。

運行結果彙總:線程分別運行的時候,並沒有受到主程序的控制。但是有了future<T>::wait()這個函數,就形成了在這個函數調用的節點,要一直等待對應的線程運行結束。正上面的例子中,連續三個異步函數對應的wait()函數就相當於將三個線程運行的結果,在此處等待運行結束,起到了彙總的作用。

相對正式一些的描述是,wait()函數增加了一個主程序的阻塞點,直到子線程執行完畢,阻塞結束。

子線程啓動時間:這裏需要注意的是std::launch::async參數,網上很多例子中,不強調這個參數,或者是採用缺省這個參數的方式。但是實際上,如果沒有這個參數,那麼異步處理基本上就無法起到多線程加速的作用。

如果不採用這個函數,那麼,子進程將在阻塞點,也就是wait()函數的位置,才啓動這個函數。也就是說,執行順序變成了:線程f1執行,f1阻塞,f1阻塞結束,f2執行,f2阻塞,f2阻塞結束,f3執行,f3阻塞,f3阻塞結束。這樣做其實相當於各個任務串行執行,沒有起到加速的作用。對於更多的細節,推薦參考《Effective Modern C++》。

2. clock()函數的坑

其實,上面的例子說完,對於異步方式實現多線程加速的過程就結束了。但是在這個過程中,筆者踩到了一個坑,就是在計算各個線程使用時間的時候,使用的計時函數clock(),不吐槽一下難以平復我的心情。

先上代碼,就是在上面的代碼中,增加了計時器,看看各個線程消耗的時間。

筆者傻乎乎地用clock()計時,運行結果讓人一臉懵*。

#include <iostream>
#include <future>
#include <ctime>
#include <thread>

using namespace std;

bool pause_thread(int n) {
    auto start_clock = clock();
    double res = 0.;
    for (double i = 0; i < 10.e9; i += 1.0) {
        res += i * i;
    }
    cout << n << " thread,clock: " << clock() - start_clock << endl;
    return true;
}

void func1() {
    auto start_clock = clock();
    future<bool> f1 = async(launch::async, pause_thread, 3);
    future<bool> f2 = async(launch::async, pause_thread, 2);
    future<bool> f3 = async(launch::async, pause_thread, 1);
    f1.wait();
    f2.wait();
    f3.wait();
    cout << "all cost: clock: " << clock() - start_clock << endl;
}

void func2() {
    auto start_clock = clock();
    pause_thread(3);
    pause_thread(2);
    pause_thread(1);
    cout << "all cost: clock: " << clock() - start_clock << endl;
}

int main() {
    cout << "---------------------func1: multi-thread" << endl;
    func1();
    cout << "---------------------func2: one-thread" << endl;
    func2();
    return 0;
}

運行結果:

---------------------func1: multi-thread
3 thread,clock: 29769532
1 thread,clock: 29777345
2 thread,clock: 29779234
all cost: clock: 29779355
---------------------func2: one-thread
3 thread,clock: 10091302
2 thread,clock: 9427059
1 thread,clock: 9277793
all cost: clock: 28796198

看到這個結果瞬間感覺自己瞎了,異步多線程用了29779355個時鐘時間,單線程串行用了28796198個時鐘時間。

經過了n久各種查,此處省略一萬匹***。發現,居然是clock()函數的鍋。這個函數不是計時用的,準確來說,是計算消耗了多少處理器時間。官方的解釋是

clock_t clock (void);

Clock program

Returns the processor time consumed by the program.

The value returned is expressed in clock ticks, which are units of time of a constant but system-specific length (with a relation of CLOCKS_PER_SEC clock ticks per second).

The epoch used as reference by clock varies between systems, but it is related to the program execution (generally its launch). To calculate the actual processing time of a program, the value returned by clock shall be compared to a value returned by a previous call to the same function.

從運行的結果上看,這個函數計算的時間,包括了此程序所有相關處理器的運行時間。至於爲什麼是這樣,這段簡短的描述了看不出來,這裏暫時不深究了。下面看一下采用chrono::system_clock::now()來計時。

#include <iostream>
#include <future>
#include <ctime>
#include <thread>
#include <chrono>

using namespace std;

bool pause_thread(int n) {
    auto start_time = chrono::system_clock::now();
    auto start_clock = clock();
    double res = 0.;
    for (double i = 0; i < 10.e9; i += 1.0) {
        res += i * i;
    }
    cout << n << " thread,clock: " << clock() - start_clock << endl;
    cout << n << " thread,sec_cost: " << (chrono::system_clock::now() - start_time).count() << endl;
    return true;
}

void func1() {
    auto start_time = chrono::system_clock::now();
    auto start_clock = clock();
    future<bool> f1 = async(launch::async, pause_thread, 3);
    future<bool> f2 = async(launch::async, pause_thread, 2);
    future<bool> f3 = async(launch::async, pause_thread, 1);
    f1.wait();
    f2.wait();
    f3.wait();
    cout << "all cost: clock: " << clock() - start_clock << endl;
    cout << "all cost: sec_cost: " << (chrono::system_clock::now() - start_time).count() << endl;
}

void func2() {
    auto start_time = chrono::system_clock::now();
    auto start_clock = clock();
    pause_thread(3);
    pause_thread(2);
    pause_thread(1);
    cout << "all cost: clock: " << clock() - start_clock << endl;
    cout << "all cost: sec_cost: " << (chrono::system_clock::now() - start_time).count() << endl;
}

int main() {
    cout << "---------------------func1: multi-thread" << endl;
    func1();
    cout << "---------------------func2: one-thread" << endl;
    func2();
    return 0;
}

運行結果:

---------------------func1: multi-thread
1 thread,clock: 29656671
1 thread,sec_cost: 2 thread,clock: 9895839
29660707
2 thread,sec_cost: 9895911
3 thread,clock: 29670203
3 thread,sec_cost: 9901171
all cost: clock: 29670278
all cost: sec_cost: 9901279
---------------------func2: one-thread
3 thread,clock: 9534692
3 thread,sec_cost: 9538701
2 thread,clock: 9259199
2 thread,sec_cost: 9262052
1 thread,clock: 9248157
1 thread,sec_cost: 9250516
all cost: clock: 28042079
all cost: sec_cost: 28051276

可以看到,在異步多線程並行的情況下,採用chrono::system_clock::now()計時的效果是正確的。單線程28051276毫秒(28秒)計算完成的,在異步多線程的情況下耗時9901279毫秒(10秒)。

參考

  1. 《Effective Modern C++》
  2. http://www.cplusplus.com/reference/ctime/clock/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章