使用条件变量

时间:2016-02-10 20:06:46

标签: multithreading opencv c++11

我正在尝试使用多个线程实现过程直方图图像的算法。

最常见的方法之一是拆分多个线程在每个线程上创建缓存缓冲区,在缓存缓冲区上执行直方图,然后锁定互斥锁,将本地值添加到输出向量,并解锁缓冲区。

这种方法非常有效,但可能引入“堵塞”。 我的意思是数据的添加无法实现。

在大多数情况下,当值的范围非常短(例如0-255)时,进行加法所需的时间非常快,可以忽略不计。

如果数据范围更高,例如在热图像上,则此时间变得更加显着。 热图像通常是无符号短矩阵,即使值不使用全范围(0-65535),算法也必须处理所有范围。

为了加快一点点处理速度,我想启动一个后台线程来进行添加,而“前台”线程只会将数据写入预先分配的缓冲区。

所以基本上“前景”线程的工作原理是:

  • 从循环缓冲区获取缓冲区。

  • 处理指定数据集的直方图(例如,从第n行到第m行)。

- 通知操作完成的后台缓冲区。

用于执行的后台线程:

  • 等到通知到达并检查可用缓冲区的数量是否低于缓冲区数。

  • 如果条件为真,则从可用的缓冲区中查找要处理的缓冲区。

  • 使用输出缓冲区进行添加。

  • 使处理过的缓冲区可重复使用。

我对条件变量不太熟悉。 因此,为了使用条件变量检查线程之间的通信,我编写了以下玩具样本:

toy.h

#ifndef TOY
#define TOY

#include <opencv2/core.hpp>
#include <opencv2/core/utility.hpp>

#include <iostream>
#include <iterator>

#include <thread>
#include <mutex>
#include <atomic>
#include <condition_variable>


namespace toy
{

class toy_t : public cv::ParallelLoopBody
{
private:

    struct elem_t
    {
        int _id;
        bool _is_available;
        bool _is_inprocess;

        inline elem_t():
            _id(-1),
            _is_available(true),
            _is_inprocess(false)
        {}
        // help for the initialization using iota.
        inline elem_t& operator=(const int& id)
        {
            this->_id = id;

            return (*this);
        }
    };

    const int _nb_thread_available;

    std::vector<elem_t> buf;
    elem_t* buf_begin;
    elem_t* buf_end;

    mutable std::atomic_size_t _nb_buffer_available;

    std::atomic_bool _run;
    std::atomic_bool _is_background_terminate;


    mutable std::mutex _mtx_fgd;
    mutable std::mutex _mtx_bgd;

    mutable std::condition_variable _cv_foreground;
    mutable std::condition_variable _cv_background;

    std::condition_variable _cv_thread;

    elem_t* get_buffer()const
    {
        // Wait untill a conditionnal variable notify that a buffer is ready to be reused.
        std::unique_lock<std::mutex> lck(this->_mtx_fgd);

        this->_cv_foreground.wait(lck,[&]{ return (this->_nb_buffer_available > 0);});

        elem_t* it = this->buf_begin;

        // Look for available buffer.

        while(!it->_is_available )
            it++;

        it->_is_available = false;
        it->_is_inprocess = true;

        this->_nb_buffer_available--;

        return it;

    }

    void background()
    {
        std::cout<<"background launch "<<std::endl;
        while(this->_run)
        {
            std::unique_lock<std::mutex> lck(this->_mtx_bgd);

            // Wait for a notification.
            this->_cv_background.wait(lck,[&]{return (this->_nb_buffer_available != cv::getNumThreads()) ;});

            //
            if(!this->_run)
                continue;

            elem_t* it = this->buf_begin;

            // Method by spining.
            // While the available buffer is not find I am looking for it.
            // When I'll find I may have done multiple pass.
            while(it->_is_available || it->_is_inprocess)
            {
                it++;

                if(it == this->buf_end)
                    it = this->buf_begin;
            }

            // This method is more logic than the spinner.
            // A condition variable has notify a buffer is ready to be reused, so a one pass check is made in order to find which is this buffer.
    //        while(!it->_is_available )
    //            it++;


            std::cout<<"the background thread is making the buffer : "<<it->_id<<" availlable."<<std::endl;

            // Do something.

            it->_is_available = true;
            it->_is_inprocess = false;

            this->_nb_buffer_available++;

            this->_cv_foreground.notify_one();
        }

        this->_is_background_terminate = true;
    }

public:

    toy_t():
        _nb_thread_available(cv::getNumThreads()), // In my computer getNumThreads() == 8
        buf(),
        buf_begin(nullptr),
        buf_end(nullptr),
        _nb_buffer_available(this->_nb_thread_available),
        _run(false),
        _is_background_terminate(false)
    {

        this->buf.reserve(this->_nb_buffer_available);
        this->buf.resize(this->buf.capacity());

        std::iota(this->buf.begin(),this->buf.end(),0);

        this->buf_begin = this->buf.data();
        this->buf_end = this->buf_begin + this->buf.size();

        std::thread th([this]{ this->_cv_thread.notify_one(); this->background();});

        this->_run = true;

        th.detach();

    }

    virtual ~toy_t()
    {
        this->_run = false;

        this->_nb_buffer_available = 0;
        this->_cv_background.notify_one();

        while(!this->_is_background_terminate)
            std::this_thread::yield();
    }

    // foreground threads
    virtual void operator()(const cv::Range& range)const
    {
            elem_t* it = this->get_buffer();

            std::cout<<"the foreground thread is processing the buffer : "<<it->_id<<std::endl;

            for(int r=range.start;r<range.end;r++)
            {
                // Do something.
            }

            std::this_thread::sleep_for(std::chrono::seconds(1));

            it->_is_inprocess = false;


            this->_cv_background.notify_one();
    }


};

}

#endif // TOY

main .cpp

#include <iostream>
#include <cstdlib>

#include "toy.h"





int main(int argc,char* argv[])
{


        toy::toy_t tt;

        cv::parallel_for_(cv::Range(0,15),tt);




    std::cout << "Hello World!" << std::endl;
    return EXIT_SUCCESS;
}

这段代码可以毫无困难地使用。

代码中不需要的方面写在方法背景上:

    // Method by spining.
    // While the available buffer is not find I am looking for it.
    // When I'll find I may have done multiple pass.
    while(it->_is_available || it->_is_inprocess)
    {
        it++;

        if(it == this->buf_end)
            it = this->buf_begin;
    }

我必须检查变量“it”的位置,否则它可能会占用缓冲区大小之外的位置。

我的想法是: - 在前台线程的末尾,将通知发送到后台线程。   - 然后后台线程处理缓冲区(或缓冲区根据线程结束的速度)。   - 最后,后台线程通知下一个前台线程(在方法get_buffer()中),它已经完成处理缓冲区并使其可重用。

当后台线程正在寻找一个线程时,在这些语句之后,它用于在Buf_start和_Buf_end之间找到它。

因此,在方法背景中寻找缓冲区的用法是:

while(!it->_is_available )
    it++;

经过几个小时的测试后,我不知道出了什么问题。 我也有兴趣知道这个算法是否真的像我认为的那样工作? 在线程之间是否有更有效,更少处理的通信方式?

提前致谢。

1 个答案:

答案 0 :(得分:1)

我解决了! :)

我从“玩具”这个类中找出了几个问题。

operator ()的重载中,我复制了方法get buffer()的代码。 我在等待条件周围做了一个块函数,以便不在线程上顺序。 条件变量所需的unique_lock仅存在于块函数的括号之间,如果已经评估了等待条件,则互斥_mtx_foreground被解锁。

然后,为了防止在搜索可用缓冲区期间出站,while循环已被for循环替换。 问题在于满足增加指针的条件。

在“后台”线程中,我添加一个循环。 这个想法是:

  1. 后台线程必须运行直到结尾 算法
  2. 后台线程正在等待通知 来自其中一个前台线程。
  3. 寻找要处理的缓冲区, 处理它,重新制作它并将其通知前台 线程。
  4. 重做直到处理完所有缓冲区然后返回 2)。
  5. 这里有些部分已被“深度”修改。

    class toy名为toy2的最新实现就是这个:

    #ifndef TOY
    #define TOY
    
    
    #include <opencv2/core.hpp>
    #include <opencv2/core/utility.hpp>
    
    #include <iostream>
    #include <iterator>
    
    #include <thread>
    #include <mutex>
    #include <atomic>
    #include <condition_variable>
    
    namespace toy
    {
    
    class toy2 : public cv::ParallelLoopBody
    {
    
    private:
    
        struct elem_t
        {
            int _id;
            std::atomic_bool _is_available;
            std::atomic_bool _is_inprocess;
    
            inline elem_t():
                _id(-1),
                _is_available(true),
                _is_inprocess(false)
            {}
    
            // needed for the memory reservation because atomic_bool copy constructor is deleted.
            inline elem_t(const elem_t& obj):
                _id(obj._id),
                _is_available((bool)obj._is_available),
                _is_inprocess((bool)obj._is_inprocess)
            {}
    
            // needed for the memory reservation because atomic_bool copy constructor is deleted.
            inline elem_t(elem_t&& obj):
                _id(obj._id),
                _is_available((bool)obj._is_available),
                _is_inprocess((bool)obj._is_inprocess)
            {}
    
            // help for the initialization using iota.
            inline elem_t& operator=(const int& id)
            {
                this->_id = id;
    
                return (*this);
            }
        };
    
        mutable std::vector<elem_t> _buffer;
        std::vector<elem_t*> _elements;
    
        std::atomic_bool _run;
    
        mutable std::atomic_size_t _nb_available_buffers;
    
        mutable std::mutex _mtx_thread;
        mutable std::mutex _mtx_foreground;
        mutable std::mutex _mtx_background;
    
        mutable std::condition_variable _cv_ctor_dtor;
        mutable std::condition_variable _cv_foreground;
        mutable std::condition_variable _cv_background;
    
    
    
        void background()
        {
            std::cout<<"background has been detach"<<std::endl;
    
    
            while(this->_run)
            {
                {
                std::unique_lock<std::mutex> lck(this->_mtx_background);
    
                this->_cv_background.wait(lck);
                }
    
                // Condition for stoping terminate the thread.
                if(!this->_run)
                    break;
    
                while(true)
                {
    
                    typename std::vector<elem_t>::iterator it = std::find_if(this->_buffer.begin(),this->_buffer.end(),[](const elem_t& v){ return (!v._is_available && !v._is_inprocess);});
    
                    if(it == this->_buffer.end())
                        break;
    
                    std::cout<<"the background is making the element : "<<it->_id<<" available."<<std::endl;
    
                    it->_is_available = true;
                    it->_is_inprocess = false;
    
                    this->_nb_available_buffers++;
    
                    this->_cv_foreground.notify_one();
                }
    
    
            }
        }
    
    public:
    
    
        toy2():
            _buffer(),
            _elements(),
            _run(false),
            _nb_available_buffers(0),
            _mtx_thread(),
            _mtx_foreground(),
            _mtx_background(),
            _cv_ctor_dtor(),
            _cv_foreground(),
            _cv_background()
        {
            const int nb_threads = cv::getNumThreads();
    
            this->_nb_available_buffers = nb_threads;
    
            this->_buffer.reserve(nb_threads);
            this->_buffer.resize(this->_buffer.capacity());
    
            this->_elements.reserve(this->_buffer.size());
            this->_elements.resize(this->_buffer.size(),nullptr);
    
    
    
            std::iota(this->_buffer.begin(),this->_buffer.end(),0);
    
            for(int i=0;i<this->_buffer.size();i++)
                this->_elements[i] = std::addressof(this->_buffer[i]);
    
            std::thread th([this]
            {
                // Notify to the constructor.
                this->_cv_ctor_dtor.notify_one();
    
                this->background();
    
                // Notify to the destructor.
                this->_cv_ctor_dtor.notify_one();
            });
    
            this->_run = true;
    
            std::unique_lock<std::mutex> lck(this->_mtx_thread);
    
            th.detach();
    
            this->_cv_ctor_dtor.wait(lck);
    
        }
    
    
        ~toy2()
        {
            this->_run = false;
            this->_cv_background.notify_one();
    
            std::unique_lock<std::mutex> lck(this->_mtx_thread);
    
            this->_cv_ctor_dtor.wait(lck);
        }
    
        void operator()(const cv::Range& range)const
        {
            {
                std::unique_lock<std::mutex> lck(this->_mtx_foreground);
    
                this->_cv_foreground.wait(lck,[&]{return this->_nb_available_buffers>0;});
            }
    
            typename std::vector<elem_t>::iterator it = std::find_if(this->_buffer.begin(),this->_buffer.end(),[](const elem_t& v){return (bool)v._is_available;});
    
    //        for(it = this->_buffer.begin();it != this->_buffer.end();it++)
    //            if(it->_is_available)
    //                break;
    
    
    
            it->_is_available = false;
            it->_is_inprocess = true;
    
            this->_nb_available_buffers--;
    
            std::cout<<"the foreground is processing the element : "<<it->_id<<" "<<std::this_thread::get_id()<<std::endl;
    
    
    
    
            std::this_thread::sleep_for(std::chrono::milliseconds(2));
    //        std::this_thread::sleep_for(std::chrono::seconds(2));
    
            it->_is_inprocess = false;
    
            this->_cv_background.notify_one();
    
            std::cout<<"end thread"<<std::endl;
        }
    
    };
    
        }
    #endif