We have been able to use the io_service
object and initialize the work
object. What we should know after this is how to give some work to the io_service
object. But before we progress to giving work to the io_service
service, we need to understand the boost::bind
library.
The Boost.Bind
library is used to ease the invocation of a function pointer. It converts the syntax from something that is abstruse and confusing to something that is easy to understand.
Let's look at the following code in order to understand how to wrap a function invocation:
/* uncalledbind.cpp */ #include <boost/bind.hpp> #include <iostream> void func() { std::cout << "Binding Function" << std::endl; } int main(void) { boost::bind(&func); return 0; }
Save the preceding code as uncalledbind.cpp
and then compile it using the following command:
g++ -Wall -ansi -I ../boost_1_58_0 uncalledbind.cpp -o uncalledbind
We will not get any line of text as output since we just created a function invocation but haven't actually called it. We have to add it to the ()
operator to call the function as follows:
/* calledbind.cpp */ #include <boost/bind.hpp> #include <iostream> void func() { std::cout << "Binding Function" << std::endl; } int main(void) { boost::bind(&func)(); return 0; }
Name the preceding code calledbind.cpp
and run the following command to compile it:
g++ -Wall -ansi -I ../boost_1_58_0 calledbind.cpp -o calledbind
Now, we will get the line of text as the output if we run the program, and of course, we will see the bind()
function as an output:
boost::bind(&func)();
As we can see in the entire code, the change is only in one line, as shown in the preceding code snippet.
Now, let's use the function that has arguments to pass. We will use boost::bind
for this purpose in the following code:
/* argumentbind.cpp */ #include <boost/bind.hpp> #include <iostream> void cubevolume(float f) { std::cout << "Volume of the cube is " << f * f * f << std::endl; } int main(void) { boost::bind(&cubevolume, 4.23f)(); return 0; }
Run the following command to compile the preceding argumentbind.cpp
file:
g++ -Wall -ansi -I ../boost_1_58_0 argumentbind.cpp -o argumentbind
We successfully call the function with the argument using boost::bind
because of which we obtain the following output:
Volume of the cube is 75.687
You need to remember that if the function has more than one argument, we have to match the function signature exactly. The following code will explain this in more detail:
/* signaturebind.cpp */ #include <boost/bind.hpp> #include <iostream> #include <string> void identity(std::string name, int age, float height) { std::cout << "Name : " << name << std::endl; std::cout << "Age : " << age << " years old" << std::endl; std::cout << "Height : " << height << " inch" << std::endl; } int main(int argc, char * argv[]) { boost::bind(&identity, "John", 25, 68.89f)(); return 0; }
Compile the signaturebind.cpp
code by using the following command:
g++ -Wall -ansi -I ../boost_1_58_0 signaturebind.cpp -o signaturebind
The signature of an identity function are std::string
, int
, and float
. So, we have to fill the bind
parameter with std::string
, int
, and float
, respectively.
Because we have matched the function signature exactly, we will obtain an output as follows:
We have already been able to call the global()
function in boost::bind
. Now, let's continue to call the function inside a class in boost::bind
. The code for this looks as follows:
/* classbind.cpp */ #include <boost/bind.hpp> #include <iostream> #include <string> class TheClass { public: void identity(std::string name, int age, float height) { std::cout << "Name : " << name << std::endl; std::cout << "Age : " << age << " years old" << std::endl; std::cout << "Height : " << height << " inch" << std::endl; } }; int main(void) { TheClass cls; boost::bind(&TheClass::identity, &cls, "John", 25, 68.89f)(); return 0; }
Compile the preceding classbind.cpp
code by using following command:
g++ -Wall -ansi -I ../boost_1_58_0 classbind.cpp -o classbind
The output for this will be exactly the same as the signaturebind.cpp
code since the content of the function is exactly the same as well:
boost::bind(&TheClass::identity, &cls, "John", 25, 68.89f)();
As we can see in the preceding code snippet, we have to pass the boost:bind
arguments with the class and function name, object of the class, and parameter based on the function signature.
So far, we have been able to use boost::bind
for the global and class functions. However, when we use the io_service
object with boost::bind
, we will get a non-copyable error because the io_service
object cannot be copied.
Now, let's take a look at multithreads.cpp
again. We will modify the code to explain the use of boost::bind
for the io_service
object and we will still need the help of the shared_ptr
pointer. Let's take a look at the following code snippet:
/* ioservicebind.cpp */ #include <boost/asio.hpp> #include <boost/shared_ptr.hpp> #include <boost/thread.hpp> #include <boost/bind.hpp> #include <iostream> void WorkerThread(boost::shared_ptr<boost::asio::io_service> iosvc, int counter) { std::cout << counter << ". "; iosvc->run(); std::cout << "End. "; } int main(void) { boost::shared_ptr<boost::asio::io_service> io_svc( new boost::asio::io_service ); 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=1; i<=5; i++) threads.create_thread(boost::bind(&WorkerThread, io_svc, i)); std::cin.get(); io_svc->stop(); threads.join_all(); return 0; }
We name the preceding code ioservicebind.cpp
and compile it using the following command:
g++ -Wall -ansi -I ../boost_1_58_0 ioservicebind.cpp -o ioservicebind –L ../boost_1_58_0/stage/lib -l boost_system-mgw49-mt-1_58 -l ws2_32 -l boost_thread-mgw49-mt-1_58
When we run ioservicebind.exe
, we obtain the same output as multithreads.exe
, but of course, the program will randomize the order of all threads:
boost::shared_ptr<boost::asio::io_service> io_svc( new boost::asio::io_service );
We instantiate the io_service
object in the shared_ptr
pointer to make it copyable so that we can bind it to the worker thread()
function that we use as a thread handler:
void WorkerThread(boost::shared_ptr<boost::asio::io_service> iosvc, int counter)
The preceding code snippet shows us that the io_service
object can be passed to the function. We do not need to define an int
global variable as we did in the multithreads.cpp
code snippet, since we can also pass the int
argument to the WorkerThread()
function:
std::cout << counter << ".
";
Now, instead of incrementing the int
variable to be shown to the user. We can use the preceding code snippet because we passed the counter from the for
loop in the main
block.
If we look at the create_thread()
function, we see the different arguments that it gets in the ioservicebind.cpp
and multithreads.cpp
files. We can pass a pointer to the void()
function that takes no arguments as the argument to the create_thread()
function, as we can see in the multithreads.cpp
file. We can also pass a binding function as an argument to the create_thread()
function, as we can see in the ioservicebind.cpp
file.
Have you ever got the following output when you ran the multithreads.exe
or ioservicebind.exe
executable files?
We can see in the preceding screenshot that there is a formatting issue here. Because the std::cout
object is a global object, writing to it from different threads at once can cause output formatting issues. To solve this issue, we can use a mutex
object that can be found in the boost::mutex
object provided by the thread
library. Mutex is used to synchronize access to any global data or shared data. To understand more about Mutex, take a look at the following code:
/* mutexbind.cpp */ #include <boost/asio.hpp> #include <boost/shared_ptr.hpp> #include <boost/thread.hpp> #include <boost/bind.hpp> #include <iostream> boost::mutex global_stream_lock; void WorkerThread(boost::shared_ptr<boost::asio::io_service> iosvc, int counter) { global_stream_lock.lock(); std::cout << counter << ". "; global_stream_lock.unlock(); iosvc->run(); global_stream_lock.lock(); std::cout << "End. "; global_stream_lock.unlock(); } int main(void) { boost::shared_ptr<boost::asio::io_service> io_svc( new boost::asio::io_service ); 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=1; i<=5; i++) threads.create_thread(boost::bind(&WorkerThread, io_svc, i)); std::cin.get(); io_svc->stop(); threads.join_all(); return 0; }
Save the preceding code as mutexbind.cpp
and then compile it using the following command:
g++ -Wall -ansi -I ../boost_1_58_0 mutexbind.cpp -o mutexbind -L ../boost_1_58_0/stage/lib -l boost_system-mgw49-mt-1_58 -l ws2_32 -l boost_thread-mgw49-mt-1_58
Now, run the mutexbind.cpp
file and we will not face the formatting issue anymore:
boost::mutex global_stream_lock;
We instantiate the new mutex
object, global_stream_lock
. With this object, we can call the lock()
and unlock()
functions. The lock()
function will block other threads that access the same function to wait for the current thread to be finished. The other threads can access the same function if only the current thread has called the unlock()
function. One thing to remember is that we should not call the lock()
function recursively because if the lock()
function is not unlocked by the unlock()
function, then thread deadlock will occur and it will freeze the application. So, we have to be careful when using the lock()
and unlock()
functions.