如何使用具有串行执行顺序的C ++线程池

时间:2014-09-12 00:03:17

标签: c++ multithreading c++11 threadpool priority-queue

我正在尝试使用使用基于优先级的任务的C ++线程池。根据优先级(它是比较器对象而不仅仅是我的情况下的值),它需要以串行方式执行,而不是仅仅调度到线程池中的下一个可用线程。

我当前的实现基于以下代码https://github.com/en4bz/ThreadPool,并且我将它作为普通线程池工作得很好(我不使用该池的优先级变体,因为我不知道如何指定一个自定义谓词对象而不是int - 如果有人可以让ke知道如何传入下面的 PriorityLevel 那将是一个真正的加号,所以相反的项目从的std :: priority_queue< T> 其中T是一个对象,它使用一个nexted PriorityLevel按优先级顺序排列项目。

任务优先级的类型按照以下行描述 - 这些由通道号,优先级字符“AZ”和可选序列号组成(如果指定,则表示任务必须等待所有更高优先级的任务完成在任务被安排在可用线程池上执行之前。我知道如何使用运算符<()严格弱排序谓词在线程池中对这些东西进行排序 - 但我不知道如何将这些元素放入其中支持执行队列。

所以在这个

的例子中
(1) channel[1] priority[A] 
(2) channel[1] priority[A] sequenceNum[1]
(3) channel[1] priority[A] sequenceNum[2] 
(4) channel[1] priority[A] sequenceNum[3] 
(5) channel[2] priority[B] 
(6) channel[2] priority[B] sequenceNum[1] 
(7) channel[2] priority[B] sequenceNum[2]

第1项和第1项5将具有最高优先级,因为它们没有先决条件 - 它们将同时运行(如果有可用线程),但其他元素必须等到它们的先决条件通道/优先级任务完成。

以下是我如何使用线程池(请注意,SLDBJob包含PriorityLevel来处理运算符<()优先级排序。

    std::priority_queue<SLDBJob> priorityJobQueue;
    //... insert a bunch of Jobs 
    // enqueue closure objects in highest to lowest priority so that the 
    // highest ones get started ahead of the lower or equal priority jobs.  
    // these tasks will be executed in priority order using rPoolSize threads
    UtlThreadPool<> threadPool(rPoolSize);
    while (!priorityJobQueue.empty()) {
        const auto& nextJob = priorityJobQueue.top();
        threadPool.enqueue(std::bind(
            &SLDBProtocol::moduleCheckingThreadFn, 
            nextJob, std::ref(gActiveJobs)));
        gActiveJobs.insert(nextJob);
        priorityJobQueue.pop();
    }

这是优先级

class PriorityLevel {
public:
    // default constructor
    explicit PriorityLevel(
        const int32_t& rChannel = -1,
        const char priority = 'Z',
        const boost::optional<int32_t>& rSequenceNum =
            boost::optional<int32_t>())
        : mChannel(rChannel)
        , mPriority(priority)
        , mSequenceNum(rSequenceNum)
    {}

    // copy constructor
    PriorityLevel(const PriorityLevel& rhs)
        : mChannel(rhs.mChannel)
        , mPriority(rhs.mPriority)
        , mSequenceNum(rhs.mSequenceNum)
    {}

    // move constructor
    PriorityLevel(PriorityLevel&& rhs)
        : mChannel(std::move(rhs.mChannel))
        , mPriority(std::move(rhs.mPriority))
        , mSequenceNum(std::move(rhs.mSequenceNum))
    {}

    // non-throwing-swap idiom
    inline void swap(PriorityLevel& rhs) {
        // enable ADL (not necessary in our case, but good practice)
        using std::swap;
        // no need to swap base members - as we are topmost class
        swap(mChannel, rhs.mChannel);
        swap(mPriority, rhs.mPriority);
        swap(mSequenceNum, rhs.mSequenceNum);
    }

    // non-throwing copy-and-swap idiom unified assignment
    PriorityLevel& operator=(PriorityLevel rhs) {
        rhs.swap(*this);
        return *this;
    }

    // equality operator
    inline bool operator==(const PriorityLevel& rhs) const {
        return std::tie(mChannel, mPriority, mSequenceNum) ==
            std::tie(rhs.mChannel, rhs.mPriority, rhs.mSequenceNum);
    }

    // inequality operator
    inline bool operator!=(const PriorityLevel& rhs) const {
        return !(operator==(rhs));
    }

    /**
     * comparator that orders the elements in the priority_queue<p>
     *
     * This is implemented via a lexicographical comparison using a
     * std::tuple<T...> as a helper. Tuple compares work as follows:
     * compares the first elements, if they are equivalent, compares
     * the second elements, if those are equivalent, compares the
     * third elements, and so on. All comparison operators are short
     * - circuited; they do not access tuple elements beyond what is
     * necessary to determine the result of the comparison. note
     * that the presence of the sequence number assigns a lower
     * priority (bigger value 1) contribution to the lexicographical
     * nature of the comparison
     *
     * @param rhs    PriorityLevel to compare against
     *
     * @return true if this is lower priority than rhs
     */
    inline bool operator<(const PriorityLevel& rhs) const {
        auto prtyLen = getPriorityStr().length();
        auto rhsPrtyLen = rhs.getPriorityStr().length();
        auto sequencePrtyVal = mSequenceNum ? mSequenceNum.get() : 0;
        auto rhsSequencePrtyVal = rhs.mSequenceNum ? rhs.mSequenceNum.get() : 0;
        return std::tie(prtyLen, mPriority, mChannel, sequencePrtyVal) >
            std::tie(rhsPrtyLen, rhs.mPriority, rhs.mChannel, rhsSequencePrtyVal);
    }

    // stream friendly struct
    inline friend std::ostream& operator << (std::ostream& os, const PriorityLevel& rValue) {
        std::string sequenceInfo;
        if (rValue.mSequenceNum) {
            sequenceInfo = std::string(", sequence[") +
                std::to_string(rValue.mSequenceNum.get()) + "]";
        }
        os  << "channel[" << rValue.mChannel
            << "], priority[" << rValue.mPriority
            << "]" << sequenceInfo;
        return os;
    }

    // channel getter
    inline int32_t getChannel() const {
        return mChannel;
    }

    // string representation of the priority string
    inline std::string getPriorityStr() const {
        std::stringstream ss;
        ss << mChannel << mPriority;
        if (mSequenceNum) {
            ss << mSequenceNum.get();
        }
        return ss.str();
    }
private:
    // the 3 fields from the ModuleNameTable::szPriorityLevel
    int32_t mChannel;
    // single upper case character A=>'highest priority'
    char mPriority;
    // optional field - when present indicates start order
    boost::optional<int32_t> mSequenceNum;
};

1 个答案:

答案 0 :(得分:2)

我不会将所有这些都放入priority_queue,因为priority_queue 非常对涉及更改优先级的事情不满意。相反,我会将1和5添加到优先级队列,并将所有其余部分放入通道的“后续映射”中,以后续任务列表。当通道1完成时,它会检查channel1是否在后续地图中有任何内容,如果是,则弹出该列表中的第一个项目,并将其添加到priority_queue。

 using ChannelID = int32_t;
 using PriorityLevel = char;

 struct dispatcher {
      std::priority_queue<SLDBJob> Todo; //starts with items 1 and 5
      std::unordered_map<ChannelID, std::vector<SLDBJob>> FollowupMap;
          //starts with {1, {2,3,4}}, {2, {6, 7, 8}}
          //note the code is actually faster if you store the followups in reverse

      void OnTaskComplete(ChannelID id) {
          auto it = FollowupMap.find(id);
          if (it != FollowupMap.end())
              if (it->empty() == false) {
                  Todo.push_back(std::move(it->front()));
                  it->erase(it->begin());
              }
              if (it->empty() == true)
                  FollowupMap.erase(it);
           }
      }
 };

用法模糊如下:

struct reportfinished {
    ChannelID id;
    ~reportfinished() {dispatcher.OnTaskComplete(id);} //check for exceptions? Your call.
};

UtlThreadPool<> threadPool(rPoolSize);
while (!priorityJobQueue.empty()) {
    const auto& nextJob = priorityJobQueue.top();
    auto wrapper = [&gActiveJobs, =]() 
        -> decltype(SLDBProtocol::moduleCheckingThreadFn(nextJob, gActiveJobs))
        {
            reportfinished queue_next{nextJob.channel};
            return SLDBProtocol::moduleCheckingThreadFn(nextJob, gActiveJobs);
        };
    threadPool.enqueue(std::move(wrapper));
    gActiveJobs.insert(nextJob);
    priorityJobQueue.pop();
}