Showing posts with label boost. Show all posts
Showing posts with label boost. Show all posts

Saturday, September 14, 2013

A basic async queueless and lockless multi threaded boost::asio producer consumer example

Sean Parents talk at this years GoingNative2013 event about "Tasks" and his critic of futures got me thinking about my use of multi threaded async asio responders

Seans talk is online at http://channel9.msdn.com/Events/GoingNative/2013/Cpp-Seasoning and it is well worth the time to watch it and the rest of the series.

I dont quite see how Seans "Tasks" idea can work without any kind of mutex/locks but i might have miss-understood him.. The asio io_service's post method is thread safe and im not certain(and to lazy to check) but it seems like that would suggest that the posted message queue inside the asio should be mutex protected... The sample code i have created here has no locks at all..

Besides that my code is unfortunately old(i just cut it down for a post out).. it still uses boosts threads and binds instead of the newer std threads and lamdbas. But hey whats a blog post without bugs, typos and caveats..

Basically this code is all about passing messages (ie a request for some jobs sub task to be done) between a producer thread and a consumer thread using the asios "post" method.

The code is a multi threaded async event processing model so it can be confusing to read. Boosts Asio offers a busy system for io_service (called boost::asio::io_service::work) to keep the threads alive but i prefer to use a async heartbeat event fired by a timer.. that way you can periodically monitor the threads health and take timed actions related to it eaiser... The core operation is that threads call into "run" and boot off the "start" to generate the initial post messages for send_msg and then the task goes on generates more "post" for both the producer and consumer sides of the job, until it reaches the end point at 3mil messages..

It uses no locks to achieve this and can pass the 3million messages(which are just ints) in about 9-10 secs on a intel i5-2450 notebook.

UPDATE: forgot to mention there is no backoff throttle between the producer and consumer so the producer can flood out the system with messages. If you want to fix that just add a Queue or Circular buffer for the messages between them. The producer times out when the queue level hits an upper threshold, and posts are sent only if the consumer is at the lower threashold/idling/or a check is done at each heartbeat.

Another option is to do this via a virtual queue(like the counters from the circular buffer) e.g use 2 message counters one for consumed and one for produced.. the delta of them is the queue message dept.. youll note that this double counter option is also lockless..

#include <iostream>

#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/thread/thread.hpp>

#include <boost/thread/mutex.hpp>
#include <boost/function.hpp>

/* RESULTS
DEBUG 116 send msg:3000000
DEBUG 56 recv msg:3000000
DEBUG 101 tock
DEBUG 90 run ended ??
DEBUG 42 tick
DEBUG 32 run ended ??

real    0m9.050s
user    0m0.000s
sys     0m0.031s
 */

const int EXIT_COUNT=3000000;

typedef uint32_t Message;

class Consumer
{
public:
    Consumer() :
        timer_(io_service_)
    {}

    void run()
    {
        // WARNING THREAD CORE LOOP!
        std::cout << "DEBUG " << __LINE__ << " run started\n";

        running_ = true;
        start();

        io_service_.run();
        running_ = false;

        std::cout << "DEBUG " << __LINE__ << " run ended ??\n";
    }

    void start()
    {
        io_service_.post(boost::bind(&Consumer::do_heartbeat, this));
    }

    void do_heartbeat()
    {
        std::cout << "DEBUG " << __LINE__ << " tick\n";
        if (running_)
        {
            timer_.expires_from_now(boost::posix_time::seconds(1));
            timer_.async_wait(boost::bind(&Consumer::do_heartbeat, this));
        }
    }

    void recv_msg(Message msg)
    {
        if (msg >= EXIT_COUNT)
            running_ = false;

        if (msg % 1000 == 0)
            std::cout << "DEBUG " << __LINE__ << " recv msg:" << msg << "\n";
    }

    void check_que(Message msg)
    {
        //WARNING external thread entry point
        io_service_.post(boost::bind(&Consumer::recv_msg, this, msg));
    }

    boost::asio::io_service io_service_;
    boost::asio::deadline_timer timer_;
    bool running_;
};


class Producer
{
public:
    Producer() :
        timer_(io_service_),
        count_(0)
    {}

    void run()
    {
        // WARNING THREAD CORE LOOP!
        std::cout << "DEBUG " << __LINE__ << " run started\n";

        running_ = true;
        start();

        io_service_.run();
        running_ = false;

        std::cout << "DEBUG " << __LINE__ << " run ended ??\n";
    }

    void start()
    {
        io_service_.post(boost::bind(&Producer::do_heartbeat, this));
        io_service_.post(boost::bind(&Producer::send_msg, this));
    }

    void do_heartbeat()
    {
        std::cout << "DEBUG " << __LINE__ << " tock\n";
        if (running_)
        {
            timer_.expires_from_now(boost::posix_time::seconds(1));
            timer_.async_wait(boost::bind(&Producer::do_heartbeat, this));
        }
    }

    void send_msg()
    {
        count_++;

        callback_(count_);

        if (count_ % 1000 == 0)
            std::cout << "DEBUG " << __LINE__ << " send msg:" << count_ << "\n";

        if (count_ > EXIT_COUNT)
            running_ = false;
        else
            io_service_.post(boost::bind(&Producer::send_msg, this));
    }

    template <class C>
    void setCallback(C callback)
    {
        callback_ = callback;
    }

    boost::function<void (Message)> callback_; // reciver for signaling..

    uint32_t count_;

    boost::asio::io_service io_service_;
    boost::asio::deadline_timer timer_;
    bool running_;
};

int main(int argc, char* argv[])
{
    try
    {
        Producer p;
        Consumer c;

        p.setCallback(boost::bind(&Consumer::check_que, &c, _1));

        boost::thread pThread(boost::bind(&Producer::run, &p ));
        boost::thread cThread(boost::bind(&Consumer::run, &c ));

        pThread.join();
        cThread.join();
    }
    catch (std::exception& e)
    {
        std::cerr << "Exception: " << e.what() << "\n";
    }

    return 0;
}

Saturday, April 28, 2012

boost 1.49 in windows 7 using mingw

Before you start. This is for(although its has worked on other systems)

mingw gcc version 4.6.2
boost verison: 1.49
OS: Windows 7 home.

This is basically the same as before in my prior post /2011/02/boost-in-vista-using-mingw-and-cmdexe.html

First get the newer version of boost from here http://sourceforge.net/projects/boost/files/boost/1.49.0/boost_1_49_0.zip/download?use_mirror=jaist


Dont bother downloading one of the ones with a build version of bjam it wont work. You will need to build it.

Setup gcc as in one of my prior posts.

Make certain that gcc is available on cmd.exe by running a fresh cmd.exe and executing:
gcc -v

You must double that this is not just a temp change to the %PATH% env variable by some script. It has to be set from windows GUI control directly to work reliably.
If gcc failed you can add it to the PATH with the following command sequence.
  • windows key+e
  • select "my computer"
  • right click it and select "properties"
  • 3rd tab -> click buttom "variables" button
  • add (or edit the existing) PATH entry and set its value [installed_dir]/mingw/bin;[installed_dir]/mingw/lib (where instal_dir is the pathto your mingw install
Next Build bjam: For help refer to: building bjam for 1.49. Note that I use the directory c:\tools as my install area for all programs that need to avoid the windows UAE etc idiocy.. Unziped the files into the desired location Then build the bjam.exe in cmd.exe by executing :
cd C:\tools\boost_1_49_0\tools\build\v2\engine
build.bat mingw
Once built copy C:\tools\boost_1_49_0\tools\build\v2\engine\bin.ntx86\b*.exe into C:\tools\MinGW\bin (This isnt needed but makes it easy later, since you likely have it in your %PATH% already.) Next build the boost libs also in cmd.exe by excuting:
cd C:\tools\boost_1_49_0
bjam toolset=gcc --build-type=complete stage
Refer: http://www.boost.org/doc/libs/1_49_0/more/getting_started/unix-variants.html Wait for the build system to grind it out. This time around there are very few build problems. I guess alot of things have been fixed since the 1.47 version. You should then build a few boost test programs(in cmd or msys) with:
g++ -I"c:\tools\boost_1_49_0" -L"c:\tools\boost_1_49_0\stage\lib" -static boost_lamba_test.cpp -o a.exe
g++ -I"c:\tools\boost_1_49_0" -L"c:\tools\boost_1_49_0\stage\lib" -static boost_regex_test.cpp -lboost_regex-mgw46-1_49 -o b.exe

The test programs are from here:

Lamba test: http://www.boost.org/doc/libs/1_45_0/more/getting_started/windows.html#build-a-simple-program-using-boost

Regex test: http://www.boost.org/doc/libs/1_45_0/more/getting_started/windows.html#link-your-program-to-a-boost-library

Keep in mind the order of the source and libs files is important in mingw http://www.mingw.org/wiki/Specify_the_libraries_for_the_linker_to_use

Monday, November 7, 2011

chomping with boost

Always convenient
    
#include <boost/algorithm/string.hpp>
...
boost::algorithm::trim_if(str,boost::algorithm::is_any_of(" \t\n"));

Wednesday, February 16, 2011

boost, in vista using mingw and cmd.exe

Gezz.. what a pain... Getting boost to build in mingw is a bit of a mess. The docs dont "officially" cover it, but it does work once you patch together all the various missing pieces. Here is how:

Mingw version: 4.5
Boost Version: 1.45
OS: Vista.

First Download the tar.bz version from http://sourceforge.net/projects/boost/files/boost/1.45.0/.
Dont download the build version of bjam it wont work. You need to build it.

Build bjam:
Refer: http://boost.org/doc/libs/1_45_0/doc/html/jam/building.html

Note that I use the directory c:\tools as my install area for all programs that need to avoid the windows UAE etc idiocy.. Transfer the unziped/untared files into the desired location and then build the bjam.exe

In cmd.exe execute:
cd C:\tools\boost_1_45_0\tools\build\v2\engine\src
build.bat mingw
Once built copy C:\tools\boost_1_45_0\tools\build\v2\engine\src\bin.ntx86\bjam.exe into C:\tools\MinGW\bin (makes it easy later, since you likely have it in your %PATH% var already.)

Build the boost libs In cmd.exe execute:
bjam toolset=gcc --build-type=complete stage
Refer: http://www.boost.org/doc/libs/1_45_0/more/getting_started/windows.html#or-build-from-the-command-prompt

Wait copious amounts of time... There seems to be ALOT of warnings simply ignore them they are "harmless"

You can build boost test programs(in cmd or msys) with:
g++ -I"c:\tools\boost_1_45_0" -L"c:\tools\boost_1_45_0\stage\lib" -static boost_lamba_test.cpp -o a.exe
g++ -I"c:\tools\boost_1_45_0" -L"c:\tools\boost_1_45_0\stage\lib" -static boost_regex_test.cpp -lboost_regex-mgw45-1_45 -o b.exe

Lamba test: http://www.boost.org/doc/libs/1_45_0/more/getting_started/windows.html#build-a-simple-program-using-boost
Regex test: http://www.boost.org/doc/libs/1_45_0/more/getting_started/windows.html#link-your-program-to-a-boost-library

Keep in mind the order of the source and libs files is important in mingw
http://www.mingw.org/wiki/Specify_the_libraries_for_the_linker_to_use