[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/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值