0

我在 std::thread 和 std::async 之间做了一个测试代码。

#include <iostream>
#include <mutex>
#include <fstream>
#include <string>
#include <memory>
#include <thread>
#include <future>
#include <functional>
#include <boost/noncopyable.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/filesystem.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/asio.hpp>

namespace fs = boost::filesystem;
namespace pt = boost::posix_time;
namespace as = boost::asio;
class Log : private boost::noncopyable
{
public:
    void LogPath(const fs::path& filePath) {
        boost::system::error_code ec;
        if(fs::exists(filePath, ec)) {
            fs::remove(filePath);
        }
        this->ofStreamPtr_.reset(new fs::ofstream(filePath));
    };

    void WriteLog(std::size_t i) {
        assert(*this->ofStreamPtr_);
        std::lock_guard<std::mutex> lock(this->logMutex_);
        *this->ofStreamPtr_ << "Hello, World! " << i << "\n";
    };

private:
    std::mutex logMutex_;
    std::unique_ptr<fs::ofstream> ofStreamPtr_;
};

int main(int argc, char *argv[]) {
    if(argc != 2) {
        std::cout << "Wrong argument" << std::endl;
        exit(1);
    }
    std::size_t iter_count = boost::lexical_cast<std::size_t>(argv[1]);

    Log log;
    log.LogPath("log.txt");

    std::function<void(std::size_t)> func = std::bind(&Log::WriteLog, &log, std::placeholders::_1);

    auto start_time = pt::microsec_clock::local_time();
    ////// Version 1: use std::thread //////
//    {
//        std::vector<std::shared_ptr<std::thread> > threadList;
//        threadList.reserve(iter_count);
//        for(std::size_t i = 0; i < iter_count; i++) {
//            threadList.push_back(
//                std::make_shared<std::thread>(func, i));
//        }
//
//        for(auto it: threadList) {
//            it->join();
//        }
//    }

//    pt::time_duration duration = pt::microsec_clock::local_time() - start_time;
//    std::cout << "Version 1: " << duration << std::endl;

    ////// Version 2: use std::async //////
    start_time = pt::microsec_clock::local_time();
    {
        for(std::size_t i = 0; i < iter_count; i++) {
            auto result = std::async(func, i);
        }
    }

    duration = pt::microsec_clock::local_time() - start_time;
    std::cout << "Version 2: " << duration << std::endl;

    ////// Version 3: use boost::asio::io_service //////
//    start_time = pt::microsec_clock::local_time();
//    {
//        as::io_service ioService;
//        as::io_service::strand strand{ioService};
//        {
//            for(std::size_t i = 0; i < iter_count; i++) {
//                strand.post(std::bind(func, i));
//            }
//        }
//        ioService.run();
//    }

//    duration = pt::microsec_clock::local_time() - start_time;
//    std::cout << "Version 3: " << duration << std::endl;


}

使用 4 核 CentOS 7 机器(gcc 4.8.5),版本 1(使用 std::thread)与其他实现相比大约慢 100 倍。

迭代版本 1 版本 2 版本 3
100 0.0034s 0.000051s 0.000066s
1000 0.038s 0.00029s 0.00058s
10000 0.41s 0.0042s 0.0059s
100000 次投掷 0.026s 0.061s

为什么线程版本这么慢?我认为每个线程不会花费很长时间来完成Log::WriteLog功能。

4

1 回答 1

2

该函数可能永远不会被调用。您没有通过std::launch版本 2 中的策略,因此您依赖于(强调我的)的默认行为:std::async

行为与 相同async(std::launch::async | std::launch::deferred, f, args...)。换句话说,它可以f在另一个线程中执行,也可以在查询结果时同步运行。std::future

尝试用这个小改动重新运行你的基准测试:

auto result = std::async(std::launch::async, func, i);

或者,您可以在第二个循环中调用result.wait()每个线程std::future,类似于join()在版本 1 中调用所有线程的方式。这会强制评估std::future.

请注意,此基准测试存在一个主要的、不相关的问题。func立即在函数调用的整个过程中获取锁,这使得并行性变得不可能。在这里使用线程没有任何优势——我怀疑它会比串行实现慢得多(由于线程创建和锁定开销)。

于 2016-03-21T04:30:36.863 回答