Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 years ago.
Improve this question
I am using g++ 4.8 to write a c++11 program. I'm trying to convert a single threaded program to multi-threaded one. The join in the threaded version ends up throwing a compilation error. Could you please let me know where I'm going wrong?
Single Threaded(works well):
Note that all arguments are pass by reference.
for (const auto& i : vec_clients)
{
i->startSim(vec_masters, vec_trace1, vec_trace2, vec_trace3);
}
Multi-Threaded version:
std::vector<std::thread> vec_thr;
for (const auto& i : vec_clients)
{
std::thread t1(&Client::startSim, std::move(i), std::move(vec_trace1), std::move(vec_trace2), std::move(vec_trace3));
vec_thr.push_back(std::move(t1));
}
for (unsigned int i=0; i<vec_thr.size(); ++i)
{
// if (i.joinable())
vec_thr.at(i).join();
}
Modified(simpler example):
class Test
{
private:
public:
void testme(const std::string& _str)
{
std::cout << "Hello "+_str << std::endl;
}
};
int main(const int argc, const char **argv)
{
std::string str = "there";
Test t1;
std::vector<std::thread> vec_thr;
std::thread thr1(&Test::testme, std::move(t1), std::cref(str));
vec_thr.push_back(thr1);
return 0;
}
Integer i does not have a joinable member function (or any member function as it is a primitive type). It should be:
for (unsigned int i=0; i<vec_thr.size(); ++i)
{
if (vec_thr[i].joinable())
vec_thr.at(i).join();
}
Or just use join on the thread. I see no particular reason to do joinable test here as you are not detaching any of the threads.
In case anyone is still interested, this is the answer:
class Test
{
private:
public:
void testme(const std::string& _str)
{
std::cout << "Hello "+_str << std::endl;
}
void testme2(const std::string& _str)
{
std::cout << "Hello to "+_str << std::endl;
}
};
int main(const int argc, const char **argv)
{
std::string str = "there";
std::unique_ptr<Test> up1(new Test());
std::unique_ptr<Test> up2(new Test());
std::vector<std::thread> vec_thr;
std::thread thr1(&Test::testme, std::move(up1), std::ref(str));
std::thread thr2(&Test::testme2, std::move(up2), std::ref(str));
vec_thr.push_back(std::move(thr1));
vec_thr.push_back(std::move(thr2));
for (unsigned int i=0; i<vec_thr.size(); ++i)
{
vec_thr.at(i).join();
}
return 0;
}
Related
I want to add a series of numbers [1-->5000] with threads. But the result is not correct.
The goal is only to understand the threading well, because I am a beginner.
I tried this:
void thread_function(int i, int (*S))
{
(*S) = (*S) + i;
}
main()
{
std::vector<std::thread> vecto_Array;
int i = 0, Som = 0;
for(i = 1; i <= 5000; i++)
{
vecto_Array.emplace_back([&](){ thread_function(i, &Som); });
}
for(auto& t: vecto_Array)
{
t.join();
}
std::cout << Som << std::endl;
}
And I tried this:
int thread_function(int i)
{
return i;
}
main()
{
std::vector<std::thread> vecto_Array;
int i = 0, Som = 0;
for(i = 1; i <= 5000; i++)
{
vecto_Array.emplace_back([&](){ Som = Som + thread_function(i); });
}
for(auto& t: vecto_Array)
{
t.join();
}
std::cout << Som << std::endl;
}
The result is always wrong. Why?
I solved the problem as follows:
void thread_function(int (*i),int (*S))
{
(*S)=(*S)+(*i);
(*i)++;
}
main()
{
std::vector<std::thread> vecto_Array;
int i=0,j=0,Som=0;
for(i=1;i<=5000;i++)
{
vecto_Array.emplace_back([&](){thread_function(&j,&Som);});
}
for(auto& t: vecto_Array)
{
t.join();
}
std::cout << Som<<std::endl;
}
But is there anyone to explain to me why it did not work when taking "i of loop" ?
Your attempt #1 has a race condition. See What is a race condition?
Your Attempt #2 neglects the standard, which says about the thread function these words:
Any return value from the function is ignored.
(see: https://en.cppreference.com/w/cpp/thread/thread/thread )
Your attempt #3 has a race condition.
Concurrent programming is an advanced topic. What you need is a book or tutorial. I first learned it from Bartosz Milewski's course: https://www.youtube.com/watch?v=80ifzK3b8QQ&list=PL1835A90FC78FF8BE&index=1
but be warned that it will likely take years before you become comfortable in concurrency. I am still not. I guess what you need as a beginner is std::async (see Milewski's tutorial or use Google). Even gentler learning curve is with OpenMP https://en.wikipedia.org/wiki/OpenMP , which could be called "parallelization for the masses".
I am trying to create a monitor class to get sum of data[]. In the class, I use a monitor a do the reading/writer working. However, the condition_variable, unique_lock and mutex confuse me. When the data size is not over 56, my code works correctly but with a bigger data size the code fails, and the condition_variable.wait(cond_lock) cannot work when debugging in lldb.
The error: type std::__1::system_error: unique_lock::unlock: not locked:
Operation not permitted
does not help me to understand the problem.
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
const int datasize = 58;//*****datasize cannot be bigger than 56*****//
int *data = new int[datasize];
void data_init(){
for (int i = 0; i < datasize; ++i) {
data[i] = random() % datasize + 1;
}
int sum = 0;
for (int i = 0; i < datasize; ++i) {
sum += data[i];
}
std::cout << "true answer: " << sum << std::endl;
}
class monitor{
public:
std::mutex the_mutex;//the mutex to lock the function
std::unique_lock<std::mutex> cond_mutex;//trying to use this for condition_variable
std::condition_variable read_to_go, write_to_go;
int active_reader, active_writer, waiting_reader, waiting_writer;
bool write_flag;
void getTask(int Rank, int& task_one, int& task_two, int& second_rank);//**reader**//
void putResult(int Rank, int the_answer, int next_Rank);//**writer**//
explicit monitor(){
write_flag = true;
active_reader = active_writer = waiting_reader = waiting_writer = 0;
}
private:
inline void startRead();
inline void endRead();
inline void startWrite();
inline void endWrite();
};
monitor imonitor;
inline void monitor::startRead() {
the_mutex.lock();//lock the function code
cond_mutex.lock();//updated 1st
while((active_writer + active_reader) > 0){//if there are working reader and writer
waiting_reader++;//add one
read_to_go.wait(cond_mutex);//wait the thread
/*****when debugging with lldb, error appears here*****/
waiting_reader--;//one less reader waiting when notified
}
active_reader++;//one more working reader
the_mutex.unlock();
}
inline void monitor::endRead() {
the_mutex.lock();
active_reader--;//one less reader working
if(active_reader == 0 && waiting_writer > 0){//if there is not any reader working and there are some writer waiting
write_to_go.notify_one();//notify one writer
}//else get out directly
the_mutex.unlock();
}
inline void monitor::startWrite() {
the_mutex.lock();
cond_mutex.lock();//updated 1st
while((active_writer + active_reader) > 0){//if any reader or writer is working
waiting_writer++;//one more writer waiting
write_to_go.wait(cond_mutex);//block this thread
waiting_writer--;//when notfied, the number of waiting writer become less
}
active_writer++;//one more active writer
the_mutex.unlock();//updated 1st
}
inline void monitor::endWrite() {//write is over
the_mutex.lock();
active_writer--;//one less writer working
if(waiting_writer > 0){//if any writer waiting
write_to_go.notify_one();//notify one of them
}
else if(waiting_reader > 0){//if any reader waiting
read_to_go.notify_all();//notify all of them
}
the_mutex.unlock();
}
void monitor::getTask(int Rank, int &task_one, int &task_two, int &second_rank) {
startRead();
task_one = data[Rank];
while(Rank < (datasize - 1) && data[++Rank] == 0);
task_two = data[Rank];
second_rank = Rank;
//std::cout << "the second Rank is " << Rank << std::endl;
endRead();
}
void monitor::putResult(int Rank, int the_answer, int next_Rank) {
startWrite();
data[Rank] = the_answer;
data[next_Rank] = 0;
endWrite();
}
void reducer(int Rank){
//std::cout << "a reducer begins" << Rank << std::endl;
do {
int myTask1, myTask2, secondRank;
imonitor.getTask(Rank, myTask1, myTask2, secondRank);
if(myTask2 == 0) return;
//std::cout << "the second value Rank: " << secondRank << std::endl;
int answer = myTask1 + myTask2;
imonitor.putResult(Rank, answer, secondRank);
}while (true);
}
int main() {
std::cout << "Hello, World!" << std::endl;
data_init();
std::thread Reduce1(reducer, 0);
std::thread Reduce2(reducer, datasize/2);
/*std::thread Reduce3(reducer, 4);
std::thread Reduce4(reducer, 6);
std::thread Reduce5(reducer, 8);
std::thread Reduce6(reducer, 10);
std::thread Reduce7(reducer, 12);
std::thread Reduce8(reducer, 14);*/
Reduce1.join(); //std::cout << "A reducer in" <<std::endl;
Reduce2.join();
/*Reduce3.join();
Reduce4.join();
Reduce5.join();
Reduce6.join();
Reduce7.join();
Reduce8.join();*/
std::cout << data[0] << std::endl;
return 0;
}
My goal used to use 8 threads, but now the code can work with only one thread. Some cout for debugging are left in the code. Thank you for any help!
Updated 1st: I add cond_mutex.lock() in startWrite() and startRead() after the_mutex.lock(). The error in the last line of startWrite about cond_mutex.unlock() is fixed, which is replaced by the_mutex.unlock(). However, the problem is not fixed.
OK, guys. Thanking for your comments, I am inspired to work this problem out.
At the very beginning, I used std::unique_lock<std::mutex> cond_mutex; in the declaration of the monitor class, meaning the default initializer of unique_lock is called.
class monitor{ public: std::mutex the_mutex, assist_lock;/***NOTE HERE***/ std::unique_lock<std::mutex> cond_mutex;/***NOTE HERE***/ std::condition_variable read_to_go, write_to_go; int active_reader, active_writer, waiting_reader, waiting_writer; ...... };
Let us check the header __mutex_base, where the mutex and unique_lock are defined.(begin at line 104)
`template
class _LIBCPP_TEMPLATE_VIS unique_lock
{
public:
typedef _Mutex mutex_type;
private:
mutex_type* _m;
bool _owns;
public:
_LIBCPP_INLINE_VISIBILITY
unique_lock() _NOEXCEPT : _m(nullptr), _owns(false) {}/first and default/
_LIBCPP_INLINE_VISIBILITY
explicit unique_lock(mutex_type& __m)
: _m(_VSTD::addressof(__m)), _owns(true) {_m->lock();}/second and own an object/
......
};`
Obviously, the first initializer is used instead of second one. __m_ is nullptr.
When the <condition_variable>.wait(cond_mutex); call the lock(), we will find the error information: "unique_lock::lock: references null mutex"(in the header __mutex_base, line 207).
When <unique_lock>.unlock() called, because the lock() does not work the bool __owns_ is false(line 116&211), we will get error information: "unique_lock::unlock: not locked"(line 257).
To fix the problem, we cannot use an all-covered unique_lock cond_mutex, but to initialize cond_mutex each time we want to use startRead() and endStart() with an object the_mutex. In this way, the <unique_lock> cond_mutex(the_mutex) is initialized by the second initializer function, at the same time the_mutex is locked and <unique_lock> cond_mutex owns the_mutex. At the end of
startRead() and endStart(), cond_mutex.unlock() unlocks itself and the_mutex, then the cond_mutex dies and releases the_mutex.
class monitor{
public:
std::mutex the_mutex;
std::condition_variable read_to_go, write_to_go;
int active_reader, active_writer, waiting_reader, waiting_writer;
bool write_flag;
void getTask(int Rank, int& task_one, int& task_two, int& second_rank);//reader
void putResult(int Rank, int the_answer, int next_Rank);//writer
explicit monitor(){
write_flag = true;
active_reader = active_writer = waiting_reader = waiting_writer = 0;
}
private:
inline void startRead();
inline void endRead();
inline void startWrite();
inline void endWrite();
};
inline void monitor::startRead() {
std::unique_lock<std::mutex> cond_mutex(the_mutex);
while((active_writer + active_reader) > 0){
waiting_reader++;
read_to_go.wait(cond_mutex);
waiting_reader--;
}
active_reader++;
cond_mutex.unlock();
}
inline void monitor::startWrite() {
std::unique_lock<std::mutex> cond_mutex(the_mutex);
while((active_writer + active_reader) > 0){
waiting_writer++;
write_to_go.wait(cond_mutex);
waiting_writer--;
}
active_writer++;
cond_mutex.unlock();
}
The solution is here, just to replace my a part of original code with the code above. After all, thank everyone who gives comments.
You seem to be writing this:
class write_wins_mutex {
std::condition_variable cv_read;
std::condition_variable cv_write;
std::mutex m;
int readers=0;
int writers=0;
int waiting_writers=0;
int waiting_readers=0;
std::unique_lock<std::mutex> internal_lock(){ return std::unique_lock<std::mutex>( m );
public:
void shared_lock() {
auto l = internal_lock();
++waiting_readers;
cv_read.wait(l, [&]{ return !readers && !writers && !waiting_writers; } );
--waiting_readers;
++readers;
}
void lock() {
auto l = internal_lock();
++waiting_writers;
cv_write.wait(l, [&]{ return !readers && !writers; } );
--waiting_writers;
++writers;
}
private:
void notify(){
if (waiting_writers) {
if(!readers) cv_write.notity_one();
}
else if (waiting_readers) cv_read.notify_all();
}
public:
void unlock_shared(){
auto l = internal_lock();
--readers;
notify();
}
void unlock(){
auto l = internal_lock();
--writers;
notify();
}
};
probably has typos; wrote this on phone without compiling it.
But this is a mutex compatible with shared_lock and unique_lock (no try lock (for) but that just means those methods don't work).
In this model, readers are shared, but if any writer shows up it gets priority (even to the level of being able to starve readers)
If you want something less biased, just use shared mutex directly.
I'm writing a simple program that consists of three threads. Each thread is passed in an object Foo and no matter which thread calls which function, the output for the program will always be "firstsecondthird". I use semaphore and I'm writing the test code for my implementation. Sometimes, my test case passed but sometimes the test case failed:
input: [1,2,3] = firstsecond
Assertion failed: (false), function test, file /home/foo/printInOrder.cc, line 100.
Abort trap: 6
My program looks like below:
#include "cpputility.h"
#include <functional>
#include <iostream>
#include <semaphore.h>
#include <sstream>
#include <string>
#include <thread>
#include <unordered_map>
#include <vector>
using namespace std;
void printFirst()
{
cout << "first" << std::flush;
}
void printSecond()
{
cout << "second" << std::flush;
}
void printThird()
{
cout << "third" << std::flush;
}
class Foo
{
protected:
sem_t firstJobDone;
sem_t secondJobDone;
public:
Foo()
{
sem_init(&firstJobDone, 0, 0);
sem_init(&secondJobDone, 0, 0);
}
void first(function<void()> printFirst)
{
printFirst();
sem_post(&firstJobDone);
}
void second(function<void()> printSecond)
{
sem_wait(&firstJobDone);
printSecond();
sem_post(&secondJobDone);
}
void third(function<void()> printThird)
{
sem_wait(&secondJobDone);
printThird();
}
};
void test()
{
unordered_map<int, pair<void (Foo::*)(function<void()>), function<void()>>> m({
{1, {&Foo::first, printFirst}},
{2, {&Foo::second, printSecond}},
{3, {&Foo::third, printThird}},
});
struct testCase
{
vector<int> input;
string expected;
};
vector<testCase> test_cases = {
{{1, 2, 3}, "firstsecondthird"},
{{1, 3, 2}, "firstsecondthird"},
};
for (auto &&test_case : test_cases)
{
std::stringstream buffer;
std::streambuf *old = std::cout.rdbuf(buffer.rdbuf());
Foo foo;
vector<thread> threads;
for (int i = 0; i < 3; ++i)
{
threads.emplace_back(m[i+1].first, foo, m[i+1].second);
}
for (auto &&th : threads)
{
th.join();
}
auto got = buffer.str();
if (got != test_case.expected)
{
printf("input: %s = %s\n",
CPPUtility::oneDVectorStr<int>(test_case.input).c_str(),
got.c_str());
assert(false);
}
std::cout.rdbuf(old);
}
}
int main()
{
for(int i = 0; i < 10; ++i) {
// Test repeatedly to detect any potential race condition
test();
}
}
The oneDVectorStr is some helper function I write inside a file called cpputility.h to help print out the 1D vector, here is the implementation to compile the code above
template <typename T>
std::string oneDVectorStr(const std::vector<T>& vec) {
std::string cand = "[";
for(int i = 0; i < vec.size(); ++i) {
cand += std::to_string(vec[i]);
i != vec.size() - 1 ? cand += "," : cand += "";
}
cand += "]";
return cand;
}
I've stared at this code for quite a while but couldn't locate any race condition. Any suggestion is welcome. Thanks in advance.
I take a further look at code and realize that there is a subtle bug in my test code: I pass Foo object (e.g., foo) by copy when create each new thread. However, I really want to have multiple threads sharing the same Foo object across multiple threads. Thus, I add std::ref to the foo:
threads.emplace_back(m[i + 1].first, ref(foo), m[i + 1].second);
In addition, I print firstJobDone and secondJobDone semaphore values using sem_getvalue() as following:
Foo()
{
sem_init(&firstJobDone, 0, 0);
sem_init(&secondJobDone, 0, 0);
int value;
sem_getvalue(&firstJobDone, &value);
printf("The initial value of the firstJobDone is %d\n", value);
sem_getvalue(&secondJobDone, &value);
printf("The initial value of the secondJobDone is %d\n", value);
}
And quite shocking, I have:
The initial value of the firstJobDone is 32766
The initial value of the secondJobDone is 32766
input: [1,2,3] = third
Assertion failed: (false), function test, file /home/foo/printInOrder.cc, line 101.
Abort trap: 6
Both semaphores are not properly initialized to 0 with LLVM on the Mac that I'm using. However, my implementation invariant insists that both semaphores have to be initilized to 0. I don't understand why but I'm assuming since sem_init is marked as deprecated by LLVM, the behavior is not guaranteed to be correct. Thus, per the comments to my question, I change my implementation using conditional variable and mutex, and everything works fine.
I'm trying to stop multiple worker threads using a std::atomic_flag. Starting from Issue using std::atomic_flag with worker thread the following works:
#include <iostream>
#include <atomic>
#include <chrono>
#include <thread>
std::atomic_flag continueFlag;
std::thread t;
void work()
{
while (continueFlag.test_and_set(std::memory_order_relaxed)) {
std::cout << "work ";
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void start()
{
continueFlag.test_and_set(std::memory_order_relaxed);
t = std::thread(&work);
}
void stop()
{
continueFlag.clear(std::memory_order_relaxed);
t.join();
}
int main()
{
std::cout << "Start" << std::endl;
start();
std::this_thread::sleep_for(std::chrono::milliseconds(200));
std::cout << "Stop" << std::endl;
stop();
std::cout << "Stopped." << std::endl;
return 0;
}
Trying to rewrite into multiple worker threads:
#include <iostream>
#include <atomic>
#include <chrono>
#include <thread>
#include <vector>
#include <memory>
struct thread_data {
std::atomic_flag continueFlag;
std::thread thread;
};
std::vector<thread_data> threads;
void work(int threadNum, std::atomic_flag &continueFlag)
{
while (continueFlag.test_and_set(std::memory_order_relaxed)) {
std::cout << "work" << threadNum << " ";
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void start()
{
const unsigned int numThreads = 2;
for (int i = 0; i < numThreads; i++) {
////////////////////////////////////////////////////////////////////
//PROBLEM SECTOR
////////////////////////////////////////////////////////////////////
thread_data td;
td.continueFlag.test_and_set(std::memory_order_relaxed);
td.thread = std::thread(&work, i, td.continueFlag);
threads.push_back(std::move(td));
////////////////////////////////////////////////////////////////////
//PROBLEM SECTOR
////////////////////////////////////////////////////////////////////
}
}
void stop()
{
//Flag stop
for (auto &data : threads) {
data.continueFlag.clear(std::memory_order_relaxed);
}
//Join
for (auto &data : threads) {
data.thread.join();
}
threads.clear();
}
int main()
{
std::cout << "Start" << std::endl;
start();
std::this_thread::sleep_for(std::chrono::milliseconds(200));
std::cout << "Stop" << std::endl;
stop();
std::cout << "Stopped." << std::endl;
return 0;
}
My issue is "Problem Sector" in above. Namely creating the threads. I cannot wrap my head around how to instantiate the threads and passing the variables to the work thread.
The error right now is referencing this line threads.push_back(std::move(td)); with error Error C2280 'thread_data::thread_data(const thread_data &)': attempting to reference a deleted function.
Trying to use unique_ptr like this:
auto td = std::make_unique<thread_data>();
td->continueFlag.test_and_set(std::memory_order_relaxed);
td->thread = std::thread(&work, i, td->continueFlag);
threads.push_back(std::move(td));
Gives error std::atomic_flag::atomic_flag(const std::atomic_flag &)': attempting to reference a deleted function at line td->thread = std::thread(&work, i, td->continueFlag);. Am I fundamentally misunderstanding the use of std::atomic_flag? Is it really both immovable and uncopyable?
Your first approach was actually closer to the truth. The problem is that it passed a reference to an object within the local for loop scope to each thread, as a parameter. But, of course, once the loop iteration ended, that object went out of scope and got destroyed, leaving each thread with a reference to a destroyed object, resulting in undefined behavior.
Nobody cared about the fact that you moved the object into the std::vector, after creating the thread. The thread received a reference to a locally-scoped object, and that's all it knew. End of story.
Moving the object into the vector first, and then passing to each thread a reference to the object in the std::vector will not work either. As soon as the vector internally reallocates, as part of its natural growth, you'll be in the same pickle.
What needs to happen is to have the entire threads array created first, before actually starting any std::threads. If the RAII principle is religiously followed, that means nothing more than a simple call to std::vector::resize().
Then, in a second loop, iterate over the fully-cooked threads array, and go and spawn off a std::thread for each element in the array.
I was almost there with my unique_ptr solution. I just needed to pass the call as a std::ref() as such:
std::vector<std::unique_ptr<thread_data>> threads;
void start()
{
const unsigned int numThreads = 2;
for (int i = 0; i < numThreads; i++) {
auto td = std::make_unique<thread_data>();
td->continueFlag.test_and_set(std::memory_order_relaxed);
td->thread = std::thread(&work, i, std::ref(td->continueFlag));
threads.push_back(std::move(td));
}
}
However, inspired by Sam above I also figured a non-pointer way:
std::vector<thread_data> threads;
void start()
{
const unsigned int numThreads = 2;
//create new vector, resize doesn't work as it tries to assign/copy which atomic_flag
//does not support
threads = std::vector<thread_data>(numThreads);
for (int i = 0; i < numThreads; i++) {
auto& t = threads.at(i);
t.continueFlag.test_and_set(std::memory_order_relaxed);
t.thread = std::thread(&work, i, std::ref(t.continueFlag));
}
}
It seems like threadsafe in my test code below. Can I use Poco::Logger in a multithreaded program?
static Poco::Logger *pLogger;
class MyRunnable : public Poco::Runnable {
private:
std::string _name;
Poco::Random _rnd;
public:
void setName(std::string name) {
_name = name;
}
void run() {
for (int i=0; i<200; i++) {
pLogger->information("info from: " + _name);
_rnd.seed(_rnd.next(65532) * _name.size());
Poco::Thread::sleep(_rnd.next(13) + 1);
}
}
};
here is test main:
int
main ( int argc, char *argv[] )
{
Poco::Thread thr1, thr2, thr3;
MyRunnable *pMyR1 = new MyRunnable(),
*pMyR2 = new MyRunnable(),
*pMyR3 = new MyRunnable();
pMyR1->setName("r1");
pMyR2->setName("ra2");
pMyR3->setName("runable3");
Poco::FormattingChannel *pFCFile = new Poco::FormattingChannel(new Poco::PatternFormatter("%Y-%m-%d %H:%M:%S.%c %N[%P]:%s: %q:%t"));
pFCFile->setChannel(new Poco::FileChannel("test.log"));
pFCFile->open();
pLogger = &(Poco::Logger::create("FileLogger", pFCFile, Poco::Message::PRIO_INFORMATION));
thr1.start(*pMyR1);
thr2.start(*pMyR2);
thr3.start(*pMyR3);
std::cout << "starting..." << std::endl;
thr1.join();
thr2.join();
thr3.join();
std::cout << "end." << std::endl;
return EXIT_SUCCESS;
} /* ---------- end of function main ---------- */
This question is very old, but I had the same doubt, so looking on the library Forum I found:
http://pocoproject.org/forum/viewtopic.php?f=12&t=1233&p=2681&hilit=logger#p2681
The important quotation is: "The Logger is thread safe regarding the different logging function. If you try to change the Channel connected to a Logger while another thread is currently using the Logger, this may lead to problems."