The core object of the Boost::Asio
namespace is io_service
. The I/O service is a channel that is used to access operating system resources and establish communication between our program and the operating system that performs I/O requests. There is also an I/O object that has the role of submitting I/O requests. For instance, the tcp::socket
object will provide a socket programming request from our program to the operating system.
One of the most frequently used functions in the I/O service object is the run()
function. It is used to run the io_service
object's event processing loop. It will block the next statement program until all the work in the io_service
object is completed and there are no more handlers to be dispatched. If we stop the io_service
object, it will no longer block the program.
Now, let's take a look at the following code snippet:
/* unblocked.cpp */ #include <boost/asio.hpp> #include <iostream> int main(void) { boost::asio::io_service io_svc; io_svc.run(); std::cout << "We will see this line in console window." << std::endl; return 0; }
We save the preceding code as unblocked.cpp
and then run the following command to compile it:
g++ -Wall -ansi -I ../boost_1_58_0 unblocked.cpp -o unblocked -L ../boost_1_58_0/stage/lib -l boost_system-mgw49-mt-1_58 -l ws2_32
When we run the program, the following output gets displayed:
We will see this line in console window.
However, why do we still obtain the line of text in the console even though previously we knew that the run()
function blocks the next function after it is invoked? This is because we have not given any work to the io_service
object. Since there is no work for io_service
to do, the io_service
object should not block the program.
Now, let's give the io_service
object some work to do. The program for this will look like as shown in the following code:
/* blocked.cpp */ #include <boost/asio.hpp> #include <iostream> int main(void) { boost::asio::io_service io_svc; boost::asio::io_service::work worker(io_svc); io_svc.run(); std::cout << "We will not see this line in console window :(" << std::endl; return 0; }
Give the preceding code the name blocked.cpp
and then compile it by typing the following command in our console window:
g++ -Wall -ansi -I ../boost_1_58_0 blocked.cpp -o blocked -L ../boost_1_58_0/stage/lib -l boost_system-mgw49-mt-1_58 -l ws2_32
If we run the program by typing blocked
in our console, we will not see the line of text anymore since we have added the following code line:
boost::asio::io_service::work work(io_svc);
The work
class is responsible for telling the io_service
object when the work starts and when it has finished. It will make sure that the run()
function in the io_service
object will not exit during the time the work is underway. Also, it will make sure that the run()
function does exit when there is no unfinished work remaining. In our preceding code, the work
class informs the io_service
object that it has work to do, but we do not define what the work is. Therefore, the program will be blocked infinitely and it will not show the output. The reason it has been blocked is because the run()
function is invoked even though we can still terminate the program by pressing Ctrl + C.
Now, we will leave the run()
function for a while and try to use the poll()
function. The poll()
function is used to run ready handlers until there are no more ready handlers remaining or until the io_service
object has been stopped. However, in contrast with the run()
function, the poll()
function will not block the program.
Let's type the following code that uses the poll()
function and save it as poll.cpp
:
/* poll.cpp */ #include <boost/asio.hpp> #include <iostream> int main(void) { boost::asio::io_service io_svc; for(int i=0; i<5; i++) { io_svc.poll(); std::cout << "Line: " << i << std::endl; } return 0; }
Then, compile poll.cpp
by using the following command:
g++ -Wall -ansi -I ../boost_1_58_0 poll.cpp -o poll -L ../boost_1_58_0/stage/lib -l boost_system-mgw49-mt-1_58 -l ws2_32
Because there is no work that the io_service
object has to do, the program should display the five lines of text as follows:
However, what if we give work to the io_service
object when we use the poll()
function? To find out the answer, let's type the following code and save it as pollwork.cpp
:
/* pollwork.cpp */ #include <boost/asio.hpp> #include <iostream> int main(void) { boost::asio::io_service io_svc; boost::asio::io_service::work work(io_svc); for(int i=0; i<5; i++) { io_svc.poll(); std::cout << "Line: " << i << std::endl; } return 0; }
To compile pollwork.cpp
, use the following command:
g++ -Wall -ansi -I ../boost_1_58_0 pollwork.cpp -o pollwork -L ../boost_1_58_0/stage/lib -l boost_system-mgw49-mt-1_58 -l ws2_32
The difference between the poll.cpp
file and the pollwork.cpp
file is only the following line:
boost::asio::io_service::work work(io_svc);
However, if we run pollwork.exe
, we will obtain the same output as that of poll.exe
. This is because, as we know from before, the poll()
function will not block the program while there is more work to do. It will execute the current work and then return the value.
We can also unblock the program by removing the work
object from the io_service
object, but we have to use a pointer to the work
object in order to remove the work
object itself. We are going to use the shared_ptr
pointer, a smart pointer provided by the Boost
libraries.
Let's use the modified code of blocked.cpp
. The code for this will be as follows:
/* removework.cpp */ #include <boost/asio.hpp> #include <boost/shared_ptr.hpp> #include <iostream> int main(void) { boost::asio::io_service io_svc; boost::shared_ptr<boost::asio::io_service::work> worker( new boost::asio::io_service::work(io_svc) ); worker.reset(); io_svc.run(); std::cout << "We will not see this line in console window :(" << std::endl; return 0; }
Save the preceding code as removework.cpp
and compile it using the following command:
g++ -Wall -ansi -I ../boost_1_58_0 removework.cpp -o removework -L ../boost_1_58_0/stage/lib -l boost_system-mgw49-mt-1_58 -l ws2_32
When we run removework.cpp
, compared to blocked.cpp
, which will block the program infinitely, the following line of text will be displayed to us:
Now, let's dissect the code. As we can see in the preceding code, we used the shared_ptr
pointer to instantiate the work
object. With this smart pointer provided by Boost, we no longer need to manually delete memory allocation in order to store the pointer since it guarantees that the object pointed to will be deleted when the last pointer is destroyed or reset. Do not forget to include shared_ptr.hpp
inside the boost
directory as the shared_ptr
pointer is defined in the header file.
We also add the reset()
function to reset the io_service
object when it prepares for a subsequent run()
function invocation. The reset()
function has to be invoked before any invocation of the run()
or poll()
functions. It will also tell the shared_ptr
pointer to automatically destroy the pointer we created. More information about the share_ptr
pointer can be found at www.boost.org/doc/libs/1_58_0/libs/smart_ptr/shared_ptr.htm.
The preceding program explains that we have successfully removed the work
object from the io_service
object. We can use this functionality if we intend to finish all the pending work even though it hasn't actually been finished yet.
We have only dealt with one thread for one io_service
object so far. If we want to deal with more threads in a single io_service
object, the following code will explain how to do this:
/* multithreads.cpp */ #include <boost/asio.hpp> #include <boost/shared_ptr.hpp> #include <boost/thread.hpp> #include <iostream> boost::asio::io_service io_svc; int a = 0; void WorkerThread() { std::cout << ++a << ". "; io_svc.run(); std::cout << "End. "; } int main(void) { boost::shared_ptr<boost::asio::io_service::work> worker( new boost::asio::io_service::work(io_svc) ); std::cout << "Press ENTER key to exit!" << std::endl; boost::thread_group threads; for(int i=0; i<5; i++) threads.create_thread(WorkerThread); std::cin.get(); io_svc.stop(); threads.join_all(); return 0; }
Give the preceding code the name mutithreads.cpp
and then compile it using the following command:
g++ -Wall -ansi -I ../boost_1_58_0 multithreads.cpp -o multithreads -L ../boost_1_58_0/stage/lib -l boost_system-mgw49-mt-1_58 -l ws2_32 -l boost_thread-mgw49-mt-1_58
We include the thread.hpp
header file so that we can use the thread
object defined inside the header file. The thread itself is a piece sequence of instructions that can be run independently, so we can run multiple threads at once.
Now, run mutithreads.exe
in our console. I obtained the following output by running it:
You might obtain a different output because all the threads that are set up as a pool of threads are equivalent to each other. The io_service
object may choose any one of them randomly and invoke its handler, so we cannot guarantee whether or not the io_service
object will choose a thread sequentially:
for(int i=0; i<5; i++) threads.create_thread(WorkerThread);
Using the preceding code snippet, we can create five threads to display lines of text as you can see in the previous screenshot. The five lines of text will be enough for this example to view the order of nonconcurrent flow:
std::cout << ++a << ". "; io_svc.run();
In every thread that is created, the program will invoke the run()
function to run the work of the io_service
object. Calling the run()
function once is insufficient because all nonworkers will be invoked after the run()
object finishes all its work.
After creating five threads, the program runs the work of the io_service
object:
std::cin.get();
After all the work is run, the program waits for you to press the Enter key from the keyboard using the preceding code snippet:
io_svc.stop();
Once the user presses the Enter key, the program will hit the preceding code snippet. The stop()
function will notify the io_service
object that all the work should be stopped. This means that the program will stop the five threads that we have:
threads.join_all();
The join_all()
function will then continue with all the unfinished threads, and the program will wait until all the processes in all the threads are complete. The preceding code snippet will continue the following statement inside the WorkerThread()
block:
std::cout << "End.
";
So after we press the Enter key, the program will finish its remaining code and we will obtain the rest of the output as follows: