Is boost::io_service::post thread safe? - c++

Is it thread safe to post new handlers from within a handler?
I.e. Can threads that called the io_service::run() post new Handlers to the same io_service?
Thanks

It is safe to post handlers from within a handler for a single instance of an io_service according to the documentation.
Thread Safety
Distinct objects: Safe.
Shared objects: Safe, with the exception that calling reset() while
there are unfinished run(), run_one(),
poll() or poll_one() calls results in
undefined behaviour.

I think it's not because the following code didn't return 3000000 and I didn't see mutex synching the internal queue of io_service neither a lock-free queue.
#include <boost/asio/io_service.hpp>
#include <boost/thread.hpp>
#include <boost/thread/detail/thread_group.hpp>
#include <memory>
void postInc(boost::asio::io_service *service, std::atomic_int *counter) {
for(int i = 0; i < 100000; i++) service->post([counter] { (*counter)++; });
}
int main(int argc, char **argv)
{
std::atomic_int counter(0);
{
boost::asio::io_service service;
boost::asio::io_service::work working(service);
boost::thread_group workers;
for(size_t i = 0; i < 10;++i) workers.create_thread(boost::bind(&boost::asio::io_service::run, &service));
boost::thread_group producers;
for (int it = 0; it < 30; it++)
{
producers.add_thread(new boost::thread(boost::bind(postInc,&service,&counter)));
}
producers.join_all();
std::cout << "producers ended" << std::endl;
service.stop();
workers.join_all();
}
std::cout << counter.load();
char c; std::cin >> c;
return 0;
}

Related

How to check whether tasks in io_service are completed?

I have a question about boost::io_service.
I have a set of tasks that I can run concurrently. After running all of them, I need to run another set of tasks concurrently. However first set has to be completed before starting to run the second set. This means I need to make sure that all the jobs submitted to io_service is completed before starting to schedule to second set.
I can implement it by keeping some kind of counter and add a busy loop but it does not look very efficient. So, I wanted to checked whether someone has a better idea or not. Following is a dummy code that I was using to experiment.
Thank you in advance!
#include <cstdio>
#include <iostream>
#include <unistd.h>
#include <boost/asio/io_service.hpp>
#include <boost/bind.hpp>
#include <boost/thread/thread.hpp>
const size_t numTasks = 100000;
void print_counter(const size_t id)
{
if (id + 1 == numTasks) {
printf("sleeping for %ld\n", id);
sleep(15);
}
printf("%ld\n", id);
}
int main(int argc, char** argv)
{
using namespace std;
using namespace boost;
asio::io_service io_service;
asio::io_service::work work(io_service);
const size_t numWorker = 4;
boost::thread_group workers;
for(size_t i = 0; i < numWorker; ++i) {
workers.create_thread(boost::bind(&asio::io_service::run, &io_service));
}
for(size_t i = 0; i < numTasks; ++i) {
io_service.post(boost::bind(print_counter, i));
}
// TODO: wait until all the tasks are done above
for(size_t i = 0; i < numTasks; ++i) {
io_service.post(boost::bind(print_counter, i));
}
// TODO: wait until all the tasks are done above
// ...
// Finally stop the service
io_service.stop();
workers.join_all();
return 0;
}
Your main problem is that all sets of your tasks are processed by the same instance of io_service. Function io_service::run returns where there is no tasks to be processed. Destructor of io_service::work informs io_service object that run can return where there are no pending tasks in queue to be performed. You can post all tasks from first set, then destroyed work and wait until io_service::run returns, then create again work object, post tasks from the next set and delete work, and so on. To do it just write helper class which may look like something below:
class TasksWaiter
{
public:
TasksWaiter(int numOfThreads)
{
work = std::make_unique<boost::asio::io_service::work>(io_service);
for(size_t i = 0; i < numOfThreads; ++i) {
workers.create_thread(boost::bind(&boost::asio::io_service::run, &io_service));
}
}
~TasksWaiter() {
work.reset();
workers.join_all();
}
template<class F>
void post(F f) {
io_service.post(f);
}
boost::thread_group workers;
boost::asio::io_service io_service;
std::unique_ptr<boost::asio::io_service::work> work;
};
int main()
{
{
TasksWaiter w1{4};
for (int i = 0; i < numTasks; ++i)
w1.post(boost::bind(print_counter,i));
// work in w1 is destroyed, then io_service::run ends
// when there are no tasks to be performed
}
printf("wait here");
{
TasksWaiter w1{4};
for (int i = 0; i < numTasks; ++i)
w1.post(boost::bind(print_counter,i));
}
}
a few remarks:
in constructor pool of threads are created
in destructor work is deleted, so io_service::run returns only if there are no pending tasks
functionality of destructor can be wrapped into a member function - e.g. wait, then you don't have to use {} scope to wait for your tasks.
From the io_service::run documentation:
The run() function blocks until all work has finished and there are no more handlers to be dispatched, or until the io_context has been stopped.
Also, from the io_context::work constructor documentation:
The constructor is used to inform the io_context that some work has begun. This ensures that the io_context object's run() function will not exit while the work is underway.
[Emphasis mine]
In short, if the run function returns and stopped returns false, then all work has been finished.

Race condition 2 threads alternating

so i want the program to ouput 1\n2\n1\n2\n1\n2\n but it seems to get stuck somewhere. But when i debug it and set a breackpoint at cv1.notify_one() right after declaring t2 it executes ??
#include <iostream>
#include <mutex>
#include <thread>
#include <condition_variable>
using namespace std;
mutex cout_lock;
condition_variable cv1, cv2;
mutex mtx1;
unique_lock<std::mutex> lck1(mtx1);
mutex mtx2;
unique_lock<std::mutex> lck2(mtx2);
const int COUNT = 3;
int main(int argc, char** argv)
{
thread t1([&](){
for(int i = 0; i < COUNT; ++i)
{
cv1.wait(lck1);
cout << "1" << endl;
cv2.notify_one();
}
});
thread t2([&](){
for(int i = 0; i < COUNT; ++i)
{
cv2.wait(lck2);
cout << "2" << endl;
cv1.notify_one();
}
});
cv1.notify_one();
t1.join();
t2.join();
return 0;
}
There are several flaws:
You want to guard your output. Therefor you need just one mutex so only one thread can do their work at a time.
You are potentially missing out notifications to your condition variables.
Your global unique_locks aquire the locks of the mutexs in their constructors. So you are holding the locks the whole time and no thread can make progress. Your global unique_locks aquire the locks of the mutexs in their constructors. This is done in the main thread. T1 and T2 are unlocking them through the condition_variable. This is undefined behaviour (thread that owns mutex must unlock it).
This is a recipe to use the condition variable approach correctly:
Have a condition you are interested in. In this case some kind of variable to remember who's turn it is.
Guard this variable by a (ONE!) mutex
Use a (ONE!) condition_variable in conjunction with the mutex of point 2 and the condition of point 1.
This ensures:
There is at any time only one thread which can look and/or change the condition you have.
If a thread is reaching the point in code where it possibly waits for the condition variable, it first checks the condition. Maybe the thread does not even need to go to sleep since the condition he wanna wait for is already true. To do so, the thread has to aquire the mutex, check the condition and decides what to do. While doing so, he owns the lock. The condition cant change because the thread has the lock itself. So you cant miss out a notification.
This leads to the following code ( see live here ):
#include <iostream>
#include <mutex>
#include <thread>
#include <condition_variable>
using namespace std;
int main(int argc, char** argv)
{
condition_variable cv;
mutex mtx;
bool runt1 = true;
bool runt2 = false;
constexpr int COUNT = 3;
thread t1([&]()
{
for(int i = 0; i < COUNT; ++i)
{
unique_lock<std::mutex> lck(mtx);
cv.wait(lck, [&](){ return runt1; });
cout << "1" << endl;
runt1 = false;
runt2 = true;
lck.unlock();
cv.notify_one();
}
});
thread t2([&]()
{
for(int i = 0; i < COUNT; ++i)
{
unique_lock<std::mutex> lck(mtx);
cv.wait(lck, [&](){ return runt2; });
cout << "2" << endl;
runt1 = true;
runt2 = false;
lck.unlock();
cv.notify_one();
}
});
t1.join();
t2.join();
return 0;
}
I think you have a data race between your threads starting and the call to cv1.notify_one(); in main().
Consider the case when cv1.notify_one() call happens before thread 1 has started and called cv1.wait(). After that no one calls cv1.notify anymore and your cv-s are just waiting. This is called Lost Wake-up.
You need a mechanism to wait in main till both threads have started, then execute cv1.notify()
Below is an example using int and a mutex.
#include "pch.h"
#include <iostream>
#include <mutex>
#include <thread>
#include <condition_variable>
using namespace std;
condition_variable cv1, cv2;
mutex m;
const int COUNT = 3;
enum Turn
{
T1,
T2
};
int main(int argc, char** argv)
{
mutex thread_start_mutex;
int num_started_threads = 0;
Turn turn = T1;
thread t1([&]() {
{
// increase the number of started threads
unique_lock<std::mutex> lck(thread_start_mutex);
++num_started_threads;
}
for (int i = 0; i < COUNT; ++i)
{
// locked cout, unlock before calling notify
{
unique_lock<std::mutex> lck1(m);
// wait till main thread calls notify
cv1.wait(lck1, [&] { return turn == T1;});
cout << "1 a really long string" << endl;
turn = T2; // next it's T2's turn
}
cv2.notify_one();
}
});
thread t2([&]() {
{
// increase the number of started threads
unique_lock<std::mutex> lck(thread_start_mutex);
++num_started_threads;
}
for (int i = 0; i < COUNT; ++i)
{
// locked cout, unlock before calling notify
{
unique_lock<std::mutex> lck2(m);
cv2.wait(lck2, [&] {return turn == T2;});
cout << "2 some other stuff to test" << endl;
turn = T1;
}
cv1.notify_one();
}
});
unique_lock<std::mutex> lck(thread_start_mutex);
// wait until both threads have started
cv1.wait(lck, [&] { return num_started_threads == 2; });
lck.unlock();
cv1.notify_one();
t1.join();
t2.join();
return 0;
}
Also it's unclear why you have two mutexes that are locked outside of main. I usually think of a mutex as something that is protected a resource that should not be accessed concurrently. Seems like the idea was to protect the cout calls, for which you should use one mutex, that each thread will lock, do the cout, unlock and notify the other one.
Edit
My original answer had exact same issue between calls to t1.notify() and t2.wait().
If t1.notify() was called before thread 2 was waiting, thread 2 never got woken up.
To address this I added an enum "Turn" which indicates who's turn it is, and each wait condition now checks if it's their turn or not.
If it is, they are not waiting and just printing out, so even if notify was missed they'd still do their task. If it is not their turn, they'll block until the other thread sets turn variable and calls notify.
NOTE: This demonstrates a good example/practice that it's usually much better to have a condition when using cv.wait(). This both makes intentions clear, and avoids both Lost Wake-up and Spurious Wake-ups.
NOTE 2 this solution might be overly complicated, and in general condition variables and mutexes are unlikely the best approach for this problem.
The other answer is right conceptually but still has another race condition. I ran the code and it would still deadlock.
The issue is that t1 is created, but it does not get to cv1.wait(lck1) until after the cv1.notify_one() executes. Thus your two threads sit together forever waiting. You demonstrate this when you put your breakpoint on that line, allowing the thread to catch up. Also, this issue persists when one thread finishes but doesn't give the other time to call wait() so it just calls notify_one. This can be seen, also fixed* (used loosely), by adding some usleep(100) calls from unistd.h.
See below:
#include <iostream>
#include <mutex>
#include <thread>
#include <condition_variable>
#include <unistd.h>
using namespace std;
mutex cout_lock;
condition_variable cv1, cv2;
mutex mtx1;
unique_lock<std::mutex> lck1(mtx1);
mutex mtx2;
unique_lock<std::mutex> lck2(mtx2);
const int COUNT = 3;
int main(int argc, char** argv)
{
thread t1([&](){
for(int i = 0; i < COUNT; ++i)
{
cv1.wait(lck1);
cout << "1\n";
usleep(100);
cv2.notify_one();
}
});
thread t2([&](){
for(int i = 0; i < COUNT; ++i)
{
cv2.wait(lck2);
cout << "2\n";
usleep(100);
cv1.notify_one();
}
});
usleep(1000);
cv1.notify_one();
t1.join();
t2.join();
return 0;
}
EDIT: To do better would be to check for waiting threads, which is not built into the mutexes you use. The proper way might be to create your own mutex wrapper class and include that functionality in the class, but for simplicity sake, I just made a waiting variable.
See below:
#include <iostream>
#include <mutex>
#include <thread>
#include <condition_variable>
#include <unistd.h>
using namespace std;
mutex cout_lock;
condition_variable cv1, cv2, cv3;
mutex mtx1;
unique_lock<std::mutex> lck1(mtx1);
mutex mtx2;
unique_lock<std::mutex> lck2(mtx2);
int waiting = 0;
const int COUNT = 3;
int main(int argc, char** argv)
{
thread t1([&](){
for(int i = 0; i < COUNT; ++i)
{
waiting++;
cv1.wait(lck1);
cout << "1\n";
waiting--;
if(!waiting)
usleep(100);
cv2.notify_one();
}
});
thread t2([&](){
for(int i = 0; i < COUNT; ++i)
{
waiting++;
cv2.wait(lck2);
cout << "2\n";
waiting--;
if(!waiting)
usleep(100);
cv1.notify_one();
}
});
if(!waiting)
usleep(100);
cv1.notify_one();
t1.join();
t2.join();
return 0;
}

Do I need to implement blocking when using boost::asio?

My question is, if I run io_service::run () on multiple threads, do I need to implement blocking on these asynchronous functions?
example:
int i = 0;
int j = 0;
void test_timer(boost::system::error_code ec)
{
//I need to lock up here ?
if (i++ == 10)
{
j = i * 10;
}
timer.expires_at(timer.expires_at() + boost::posix_time::milliseconds(500));
timer.async_wait(&test_timer);
}
void threadMain()
{
io_service.run();
}
int main()
{
boost::thread_group workers;
timer.async_wait(&test_timer);
for (int i = 0; i < 5; i++){
workers.create_thread(&threadMain);
}
io_service.run();
workers.join_all();
return 0;
}
The definition of async is that it is non-blocking.
If you mean to ask "do I have to synchronize access to shared objects from different threads" - that question is unrelated and the answer depends on the thread-safety documented for the object you are sharing.
For Asio, basically (rough summary) you need to synchronize concurrent access (concurrent as in: from multiple threads) to all types except boost::asio::io_context¹,².
Your Sample
Your sample uses multiple threads running the io service, meaning handlers run on any of those threads. This means that effectively you're sharing the globals and indeed they need protection.
However Because your application logic (the async call chain) dictates that only one operation is ever pending, and the next async operation on the shared timer object is always scheduled from within that chain, the access is logically all from a single thread (called an implicit strand. See Why do I need strand per connection when using boost::asio?
The simplest thing that would work:
Logical Strand
Live On Coliru
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <iostream>
boost::asio::io_service io_service;
boost::asio::deadline_timer timer { io_service };
struct state_t {
int i = 0;
int j = 0;
} state;
void test_timer(boost::system::error_code ec)
{
if (ec != boost::asio::error::operation_aborted) {
{
if (state.i++ == 10) {
state.j = state.i * 10;
if (state.j > 100)
return; // stop after 5 seconds
}
}
timer.expires_at(timer.expires_at() + boost::posix_time::milliseconds(50));
timer.async_wait(&test_timer);
}
}
int main()
{
boost::thread_group workers;
timer.expires_from_now(boost::posix_time::milliseconds(50));
timer.async_wait(&test_timer);
for (int i = 0; i < 5; i++){
workers.create_thread([] { io_service.run(); });
}
workers.join_all();
std::cout << "i = " << state.i << std::endl;
std::cout << "j = " << state.j << std::endl;
}
Note I removed the io_service::run() from the main thread as it is redundant with the join() (unless you really wanted 6 threads running the handlers, not 5).
Prints
i = 11
j = 110
Caveat
There's a pitfall lurking here. Say, you didn't want to bail at a fixed number, like I did, but want to stop, you'd be tempted to do:
timer.cancel();
from main. That's not legal, because the deadline_timer object is not thread safe. You'd need to either
use a global atomic_bool to signal the request for termination
post the timer.cancel() on the same strand as the timer async chain. However, there is only an explicit strand, so you can't do it without changing the code to use an explicit strand.
More Timers
Let's complicate things by having two timers, with their own implicit strands. This means access to the timer instances still need not be synchronized, but access to i and j does need to be.
Note In this demo I use synchronized_value<> for elegance. You can write similar logic manually using mutex and lock_guard.
Live On Coliru
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <boost/thread/synchronized_value.hpp>
#include <iostream>
boost::asio::io_service io_service;
struct state {
int i = 0;
int j = 0;
};
boost::synchronized_value<state> shared_state;
struct TimerChain {
boost::asio::deadline_timer _timer;
TimerChain() : _timer{io_service} {
_timer.expires_from_now(boost::posix_time::milliseconds(50));
resume();
}
void resume() {
_timer.async_wait(boost::bind(&TimerChain::test_timer, this, _1));
};
void test_timer(boost::system::error_code ec)
{
if (ec != boost::asio::error::operation_aborted) {
{
auto state = shared_state.synchronize();
if (state->i++ == 10) {
state->j = state->i * 10;
}
if (state->j > 100) return; // stop after some iterations
}
_timer.expires_at(_timer.expires_at() + boost::posix_time::milliseconds(50));
resume();
}
}
};
int main()
{
boost::thread_group workers;
TimerChain timer1;
TimerChain timer2;
for (int i = 0; i < 5; i++){
workers.create_thread([] { io_service.run(); });
}
workers.join_all();
auto state = shared_state.synchronize();
std::cout << "i = " << state->i << std::endl;
std::cout << "j = " << state->j << std::endl;
}
Prints
i = 12
j = 110
Adding The Explicit Strands
Now it's pretty straight-forward to add them:
struct TimerChain {
boost::asio::io_service::strand _strand;
boost::asio::deadline_timer _timer;
TimerChain() : _strand{io_service}, _timer{io_service} {
_timer.expires_from_now(boost::posix_time::milliseconds(50));
resume();
}
void resume() {
_timer.async_wait(_strand.wrap(boost::bind(&TimerChain::test_timer, this, _1)));
};
void stop() { // thread safe
_strand.post([this] { _timer.cancel(); });
}
// ...
Live On Coliru
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <boost/thread/synchronized_value.hpp>
#include <iostream>
boost::asio::io_service io_service;
struct state {
int i = 0;
int j = 0;
};
boost::synchronized_value<state> shared_state;
struct TimerChain {
boost::asio::io_service::strand _strand;
boost::asio::deadline_timer _timer;
TimerChain() : _strand{io_service}, _timer{io_service} {
_timer.expires_from_now(boost::posix_time::milliseconds(50));
resume();
}
void resume() {
_timer.async_wait(_strand.wrap(boost::bind(&TimerChain::test_timer, this, _1)));
};
void stop() { // thread safe
_strand.post([this] { _timer.cancel(); });
}
void test_timer(boost::system::error_code ec)
{
if (ec != boost::asio::error::operation_aborted) {
{
auto state = shared_state.synchronize();
if (state->i++ == 10) {
state->j = state->i * 10;
}
}
// continue indefinitely
_timer.expires_at(_timer.expires_at() + boost::posix_time::milliseconds(50));
resume();
}
}
};
int main()
{
boost::thread_group workers;
TimerChain timer1;
TimerChain timer2;
for (int i = 0; i < 5; i++){
workers.create_thread([] { io_service.run(); });
}
boost::this_thread::sleep_for(boost::chrono::seconds(10));
timer1.stop();
timer2.stop();
workers.join_all();
auto state = shared_state.synchronize();
std::cout << "i = " << state->i << std::endl;
std::cout << "j = " << state->j << std::endl;
}
Prints
i = 400
j = 110
¹ (or using the legacy name boost::asio::io_service)
² lifetime mutations are not considered member operations in this respect (you have to manually synchronize construction/destruction of shared objects even for thread-safe objects)

boost asio and condition variables -- strange output

Suggest that I have the following code:
#include <boost/asio/io_service.hpp>
#include <boost/thread.hpp>
#include <condition_variable>
#include <iostream>
#include <mutex>
const int THREAD_POOL_SIZE = 2;
std::condition_variable g_cv;
std::mutex g_cv_mutex;
bool g_answer_ready;
void foo()
{
std::cout << "foo \n";
std::unique_lock<std::mutex> lock(g_cv_mutex);
g_answer_ready = true;
g_cv.notify_all();
}
int main()
{
boost::asio::io_service io_service;
for (int i = 0; i < 10; ++i)
{
std::auto_ptr<boost::asio::io_service::work> work(new boost::asio::io_service::work(io_service));
boost::thread_group threads;
for (int i = 0; i < THREAD_POOL_SIZE; ++i)
{
threads.create_thread(boost::bind(&boost::asio::io_service::run, &io_service));
}
std::unique_lock<std::mutex> lock(g_cv_mutex);
io_service.post(foo);
g_answer_ready = false;
g_cv.wait_for(lock, std::chrono::milliseconds(2000));
if (!g_answer_ready)
{
std::cout << "timed_out \n";
}
io_service.stop();
threads.join_all();
}
}
The output will be different between program's launches. For example,
foo
timed_out
foo
foo
However, if I move boost::asio::io_service object construction inside the loop, it works as expected:
foo
foo
foo
foo
foo
foo
foo
foo
foo
foo
Why? What am I doing wrong? How can I fix it?
boost 1.54, MSVC-11.0
If I understand you correctly, you need to fix last lines on your loop (see comments for descriptions):
// io_service.stop();
// threads.join_all();
work.reset(); // <- signal to process all pending jobs and quit from io_service::run function
threads.join_all(); // <- wait for all threads
io_service.reset(); // <- now `io_service` can accept new tasks
So, there were two issues in the original code:
io_service.stop() will cancel posted but not yet processed jobs (usually this is not what a programmer wants),
io_service.reset() is required to change the state of io_service from "stopped" to "ready to accept new jobs".

Using Boost threads and io_service to create a threadpool

I have looked around Stack Overflow and there have been a few really good answers on this, (my code is actually based on this answer here) but for some reason I am getting weird behavior - in that thread_func should be called ls1 times, but it is only running between 0 and 2 times before the threads exit. It seems like ioService.stop() is cutting off queued jobs before they are completed, but from what I understand this should not happen. Here is the relevant code snippet:
boost::asio::io_service ioService;
boost::asio::io_service::work work(ioService);
boost::thread_group threadpool;
for (unsigned t = 0; t < num_threads; t++)
{
threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioService));
}
//Iterate over the dimensions of the matrices
for (unsigned i = 0; i < ls1; i++)
{
ioService.post(boost::bind(&thread_func,i, rs1, rs2, ls2, arr, left_arr, &result));
}
ioService.stop();
threadpool.join_all();
Any help would be greatly appreciated, thanks!
io_service::stop() causes all invocations of run() or run_one() to return as soon as possible. It does not remove any outstanding handlers that are already queued into the io_service. When io_service::stop() is invoked, the threads in threadpool will return as soon as possible, causing each thread of execution to be complete.
As io_service::post() will return immediately after requesting that the io_service invoke the handler, it is non-deterministic as to how many posted handlers will be invoked by threads in threadpool before the io_service is stopped.
If you wish for thread_func to be invoked ls1 times, then one simple alternative is to reorganize the code so that work is added to the io_service before the threadpool services it, and then the application lets the io_service run to completion.
boost::asio::io_service ioService;
// Add work to ioService.
for (unsigned i = 0; i < ls1; i++)
{
ioService.post(boost::bind(
&thread_func,i, rs1, rs2, ls2, arr, left_arr, &result));
}
// Now that the ioService has work, use a pool of threads to service it.
boost::thread_group threadpool;
for (unsigned t = 0; t < num_threads; t++)
{
threadpool.create_thread(boost::bind(
&boost::asio::io_service::run, &ioService));
}
// Once all work has been completed (thread_func invoked ls1 times), the
// threads in the threadpool will be completed and can be joined.
threadpool.join_all();
If you're expecting thread_func to be called ls1 times, then you should wait until it is actually called ls1 times before stopping io_service. As written, stop() may be called before any of the threads had a chance to have been scheduled even.
There are many ways to wait for that condition. For example you could use a condition variable:
#include <boost/asio.hpp>
#include <boost/thread.hpp>
unsigned num_threads = 10, ls1=11;
int result = 0;
boost::mutex m;
boost::condition_variable cv;
void thread_func(unsigned , int* result) {
/* do stuff */
{
boost::lock_guard<boost::mutex> lk(m);
++*result;
}
cv.notify_one();
}
int main()
{
boost::asio::io_service ioService;
boost::asio::io_service::work work(ioService);
boost::thread_group threadpool;
for (unsigned t = 0; t < num_threads; t++)
threadpool.create_thread(boost::bind(&boost::asio::io_service::run,
&ioService));
for (unsigned i = 0; i < ls1; i++)
ioService.post(boost::bind(&thread_func,i,&result));
{
boost::unique_lock<boost::mutex> lk(m);
cv.wait(lk, []{return result == ls1; });
}
ioService.stop();
threadpool.join_all();
std::cout << "result = " << result << '\n';
}