[C++] POCO learning summary (6): threads, thread pools, synchronization

[C++] Guo Laoer’s blog post: C++ directory

1. Thread

1.1 Affiliated libraries and header files

The thread class in Poco is Poco::Thread. In the basic library Foundation, it corresponds to the dynamic library libPocoFoundation.so;
When using it, you need to include the header file: #include “Poco/ Thread.h”

1.2 Properties

1.2.1 Name and ID

You can give each thread a name (through the constructor, or setName());
Each thread has a unique ID
Related functions: getName(), setName(), id()

1.2.2 Priority

Each thread can be assigned a priority. POCO defines five levels of priority:

  • PRIO_LOWEST - lowest thread priority.
  • PRIO_LOW - Lower than normal thread priority.
  • PRIO_NORMAL - Normal thread priority.
  • PRIO_HIGH - Higher than normal thread priority.
  • PRIO_HIGHEST——Highest thread priority.

Note: Some platforms require special permissions (root) to set or change a thread's priority.
If the five-level priority provided by POCO is not enough, you can use setOSPriority() to set the thread priority of a specific operating system;
You can use getMinOSPriority() and getMaxOSPriority() to find out the valid range of priority values;

1.2.3 Thread stack size

The stack size of the thread can be set through setStackSize(int size);
If size is 0, the operating system's default stack size is used;
The getStackSize() function returns the stack size of the specified thread;

1.2.4 Other commonly used functions

isRunning(): Check whether the thread is running;
current(): Returns the Thread object pointer of the current thread. Note: The main thread does not have a thread object, so a null pointer is returned; < /span> Thread::yield(): Give up the CPU "time slice" grabbed by the current thread to other threads< /span>
Thread::sleep(): Tentative

1.3 Poco::Runnable

1.3.1 Usage

Qters who are familiar with Qt should be familiar with the usage of QThread:

  • Subclassing QThread
  • Override virtual function run()
  • Call start() to start the thread

Coincidentally, the usage of Poco::Runnable is very similar to QThread.
The official introduction is: Poco::Runnable is an interface class (needs to rewrite run()), which implements the thread entry function.
When using, you need to add the header file: #include "Poco/Runnable.h"

1.3.2 Example

#include "Poco/Thread.h"
#include "Poco/Runnable.h"
#include <iostream>
class HelloRunnable: public Poco::Runnable
{
    
    
	virtual void run()
	{
    
    
		std::cout << "Hello, world!" << std::endl;
	}
};
int main(int argc, char** argv)
{
    
    
	HelloRunnable runnable;
	Poco::Thread thread;
	thread.start(runnable);
	thread.join();
	return 0;
}

1.4 Poco::RunnableAdapter

1.4.1 Usage

Poco::RunnableAdapter is a class template and an adapter, which can put parameterless member functions into threads to run.
Principle: Poco::RunnableAdapter inherits from Poco::Runnable, and the class and method are specified in the Poco::RunnableAdapter constructor; then the method is called in run(), the pseudo code is as follows :

Poco::RunnableAdapter(C& object, Callback method): _pObject(&object), _method(method)
void run()
{
	(_pObject->*_method)();
}

1.4.2 Example

#include "Poco/Thread.h"
#include "Poco/RunnableAdapter.h"
#include <iostream>
class Laoer
{
    
    
public:
    void say()
   {
    
    
       std::cout << "Hello, world!" << std::endl;
   }
};
int main(int argc, char** argv)
{
    
    
    Laoer laoer;
    Poco::RunnableAdapter<Laoer> runnable(laoer, &Laoer::say);
    Poco::Thread thread;
    thread.start(runnable);
    thread.join();
    return 0;
}

2. Thread pool

2.1 Benefits of using thread pool

1) Save overhead: Creating a new thread takes some time, and reusing threads can save the time and resources of repeated creation.
2) Simple management: no need to be distracted to manage the life cycle of thread objects
3) Control the number of threads: you can control the number of threads

2.2 Usage

The thread pool class in POCO is Poco::ThreadPool, header file: #include "Poco/ThreadPool.h";
The thread pool has a maximum capacity. If the capacity is exhausted, An exception is thrown when requesting a new thread: Poco::NoThreadAvailableException.
The thread pool capacity can be increased dynamically: void addCapacity(int n)
POCO provides a default ThreadPool instance with an initial capacity of 16 threads.

When threads in the thread pool are idle for a certain period of time, they will be automatically collected; you can also force collection by calling collect().

2.3 Example

#include "Poco/ThreadPool.h"
#include "Poco/Runnable.h"
#include <iostream>
class HelloRunnable: public Poco::Runnable
{
    
    
	virtual void run()
	{
    
    
		std::cout << "Hello, world!" << std::endl;
	}
};
int main(int argc, char** argv)
{
    
    
	HelloRunnable runnable;
	Poco::ThreadPool::defaultPool().start(runnable);
	Poco::ThreadPool::defaultPool().joinAll();
	return 0;
}

3. Thread local variables

3.1 Description

Poco::ThreadLocal is a thread local variable, also called thread local storage, which means that the variable filled in the template ThreadLocal belongs to the current thread. This variable is isolated from other threads, which means that the variable is unique to the current thread. .

3.2 Example

#include "Poco/Thread.h"
#include "Poco/Runnable.h"
#include "Poco/ThreadLocal.h"
#include <iostream>

class Counter: public Poco::Runnable
{
    
    
	void run()
	{
    
    
		static Poco::ThreadLocal<int> tls;
		for (*tls = 0; *tls < 10; ++(*tls))
		{
    
    
			std::cout << Poco::Thread::current()->id() << ":" << *tls << std::endl;
		} 
	}
};

int main(int argc, char** argv)
{
    
    
	Counter counter;
	Poco::Thread t1;
	Poco::Thread t2;
	t1.start(counter);
	t2.start(counter);
	t1.join();
	t2.join();
	return 0;
}

Compile:

g++ thread.cpp -I ~/git/poco/install/include -L ~/git/poco/install/lib -lPocoFoundationd -lpthread

Output:

1:0
1:1
……
1:8
1:9
2:0
2:1
……
2:8
2:9

4. Thread error handling

4.1 Description

Unhandled exceptions in the thread will cause the thread to terminate. However, such unhandled exceptions are usually not reported externally.
You can get unhandled exceptions by registering a global error handler.

4.2 Example

$ vi threadErrHeadle.cpp

#include "Poco/Thread.h"
#include "Poco/Runnable.h"
#include "Poco/ErrorHandler.h"
#include <iostream>

using namespace Poco;

class Offender: public Poco::Runnable
{
    
    
	void run()
	{
    
    
		throw Poco::ApplicationException("got you");
	}
};

class MyErrorHandler: public Poco::ErrorHandler
{
    
    
	public:
		void exception(const Poco::Exception& exc)
		{
    
    
			std::cerr << exc.displayText() << std::endl;
		}
		void exception(const std::exception& exc)
		{
    
    
			std::cerr << exc.what() << std::endl;
		}
		void exception()
		{
    
    
			std::cerr << "unknown exception" << std::endl;
		}
};

int main(int argc, char** argv)
{
    
    
	MyErrorHandler eh;
	ErrorHandler* pOldEH = Poco::ErrorHandler::set(&eh);
	Offender offender;
	Thread thread;
	thread.start(offender);
	thread.join();
	Poco::ErrorHandler::set(pOldEH);
	return 0;
}

Compile:

g++ threadErrHeadle.cpp -I ~/git/poco/install/include -L ~/git/poco/install/lib -lPocoFoundationd -lpthread

5. Thread synchronization

5.1 Poco::Mutex recursive mutex lock

5.1.1 Description

Poco::Mutex is a recursive mutex: the same mutex can be locked multiple times by the same thread (but not by other threads)

Related functions:
1) lock(): Obtain the mutex lock, if the mutex lock is held by other threads, wait;
2) lock(long millisecs): Obtain the mutex lock. If the mutex lock is held by another thread, block to the given number of milliseconds. TimeoutException is thrown when timeout occurs;
3) unlock(): Release the mutex lock so that it can be acquired by another thread;
4) tryLock(): Try to acquire the mutex lock. If the mutex is held by another thread, false is returned immediately; if the mutex has been acquired, true is returned immediately.
5) tryLock(long millisecs): Try to acquire the mutex lock within a given time period. Returns false if the lock acquisition fails, returns true if the mutex lock has been acquired.

5.1.2 Poco::Mutex::ScopedLock

Scope lock: that is, using the scope, locking in the Poco::Mutex::ScopedLock constructor and unlocking in the destructor

5.1.3 Example

#include "Poco/Mutex.h"
using Poco::Mutex;
class Concurrent
{
    
    
	public:
	void criticalSection()
	{
    
    
		Mutex::ScopedLock lock(_mutex);
		// ...
	}
private:
	Mutex _mutex;
};

5.2 Poco::FastMutex non-recursive mutex lock

Poco::FastMutex is a non-recursive mutex: when the same mutex is locked again, it will cause a deadlock.

The API and usage methods are the same as Poco::Mutex.

5.3 Poco::Event event

5.3.1 Description

Poco::Event is a synchronization object that allows one thread to signal the occurrence of an event to one or more other threads.Similar to semaphore
Poco::Event has two forms:

  • auto reset: After waking up at most one waiting thread, the event will lose its signal status
  • manual reset: The event will remain signaled until manually reset

5.3.2 Usage

Header file: #include “Poco/Event.h”
Poco::Event supports automatic reset and manual reset:

  • For automatic reset (default), pass true to the constructor.
  • For manual reset, pass false to the constructor.

1) set(): Send event signal. If the event is automatically reset, at most one thread waiting for the event is awakened and the signaling state is reset. Otherwise, all threads waiting for the event will be awakened.
2) wait() and wait(long milliseconds): Wait for the event to become a signal. If a timeout is given and the event is not signaled within the given interval, TimeOutException
is thrown. 3) tryWait(long milliseconds): Wait for the event to become signaled. Returns true if the event is signaled within the given interval. Otherwise, return false
4) reset(): reset (manual reset) event

5.4 Poco::Condition condition variable

5.4.1 Description

Poco::Condition is used to block a thread until a specific condition is met.
Poco::Condition is always used with Mutex (or FastMutex).
Poco::Condition is similar to POSIX condition variable, the difference is that the condition is not affected by false arousalImpact.

Threads waiting for conditions are resumed in FIFO order

5.4.2 Usage

Header file: #include "Poco/Condition.h
Poco::Condition is template-based and suitable for any type of mutex object. The implementation is based on Poco::Event and std::deque
for waiting threads on all platforms 1) Wait():

template <class Mtx> void wait(Mtx& mutex)
template <class Mtx> void wait(Mtx& mutex, long milliseconds)

unlocks the mutex (which must be locked when wait() is called) and waits (the given time) until Condition signals.
The given mutex will be locked again after successfully leaving the function, even in the case of an exception.
Throws TimeoutException if the condition is not signaled within the given time interval.

2) Try to wait tryWait: no exception will be thrown

template <class Mtx> bool tryWait(Mtx& mutex, long millisecs)

3) Signal signal()
Issues a conditional signal and allows a waiting thread to continue executing.

4) Broadcast broadcast()
emits a conditional signal and allows all waiting threads to continue execution.

5.5 Poco::Semaphore semaphore

5.5.1 Description

5.5.2 Usage

Header file: #include “Poco/Semaphore.h”

5.6 Poco::RWLock read-write lock

5.6.1 Description

Poco::RWLock allows multiple concurrent reads, or an exclusive write.

5.6.1 Usage

Header file: #include "Poco/RWLock.h
Commonly used functions:
1) void readLock(): Obtain the read lock. If another thread holds the write lock, wait until the write lock is released
2) bool tryReadLock(): Obtain the read lock. If another thread holds the write lock, return false immediately
3) void writeLock(): Obtain the write lock. If other threads currently hold locks, wait until all locks are released
4) bool tryWriteLock(): Obtain the write lock. If other threads currently hold the lock, return false immediately
5) void unlock(): unlock

6. Advanced usage of threads

6.1 Poco::Timer sequence timer

6.1.1 Description

The timer starts a thread (obtained from the thread pool) that first waits for the given start interval.
Once the start interval expires, the timer will call the callback function.
After the callback function returns and the period interval is not zero, the timer repeatedly waits for the period interval and then calls the callback function.

6.1.2 Usage

Header file: #include “Poco/Timer.h”
Please pay attention when using it

  • The timer can be stopped by setting the period interval to zero.
  • Timer callback functions run in the timer's thread, so synchronization may be required.
  • The accuracy of the timer depends on many factors, such as operating system, system load, etc., so the real-time performance is poor.

6.1.3 Example

$ we hours.cpp

#include "Poco/Timer.h"
#include "Poco/Thread.h"
#include <iostream>

using Poco::Timer;
using Poco::TimerCallback;

class TimerExample
{
    
    
	public:
		void onTimer(Poco::Timer& timer)
		{
    
    
			std::cout << "onTimer called." << std::endl;
		}
};

int main(int argc, char** argv)
{
    
    
	TimerExample te;
	Timer timer(250, 500); // 250ms后启动,每500ms重复一次
	timer.start(TimerCallback<TimerExample>(te, &TimerExample::onTimer));
	Poco::Thread::sleep(5000);
	timer.stop();
	return 0;
}

compile

g++ timer.cpp -I ~/git/poco/install/include -L ~/git/poco/install/lib -lPocoFoundationd -lpthread

output

$ ./a.out 
onTimer called.
onTimer called.
onTimer called.
……

6.2 Poco::TaskManager task management

6.2.1 Description

For example: When you need to track the progress of one or more background processing threads in a GUI (or server) application, you can use Poco::Task and Poco::TaskManager

Poco::Task is a Poco::Runnable that provides facilities for reporting the progress of operations and supports task cancellation.
Poco::TaskManager manages a collection of Poco::Task objects (SharedPtr) and uses Poco::ThreadPool to run them.

6.2.2 Usage

1)头文件
#include “Poco/Task.h”
#include “Poco/TaskManager.h”

2) Create a subclass of Poco::Task and rewrite the runTask() member function.
From your runTask(), you periodically call setProgress() to report the progress of the task, and call isCancelled() or sleep() to check for cancellation requests.

3) Poco::TaskManager uses Poco::NotificationCenter to notify relevant parties about the progress of its tasks.
Common notification types

  • Poco::TaskStartedNotification
  • Poco::TaskCancelledNotification
  • Poco::TaskFinishedNotification
  • Poco::TaskFailedNotification
  • Poco::TaskProgressNotification
  • Poco::TaskCustomNotification

6.2.3 Example

$ vi task.cpp

#include "Poco/Task.h"
#include "Poco/TaskManager.h"
#include "Poco/TaskNotification.h"
#include "Poco/Observer.h"
#include <iostream>

using Poco::Observer;

class SampleTask: public Poco::Task
{
    
    
	public:
		SampleTask(const std::string& name): Task(name) {
    
    }
		void runTask()
		{
    
    
			for (int i = 0; i < 100; ++i)
			{
    
    
				setProgress(float(i)/100); // report progress
				if (sleep(10))
					break;
			} 
		}
};

class ProgressHandler
{
    
    
	public:
		void onProgress(Poco::TaskProgressNotification* pNf)
		{
    
    
			std::cout << pNf->task()->name() << " progress: " << pNf->progress() << std::endl;
			pNf->release();
		}
		void onFinished(Poco::TaskFinishedNotification* pNf)
		{
    
    
			std::cout << pNf->task()->name() << " finished." << std::endl;
			pNf->release();
		}
};

int main(int argc, char** argv)
{
    
    
	Poco::TaskManager tm;
	ProgressHandler pm;
	tm.addObserver(
			Poco::Observer<ProgressHandler, Poco::TaskProgressNotification>
			(pm, &ProgressHandler::onProgress)
			);
	tm.addObserver(
			Poco::Observer<ProgressHandler, Poco::TaskFinishedNotification>
			(pm, &ProgressHandler::onFinished)
			);
	tm.start(new SampleTask("Task 1")); // tm takes ownership
	tm.start(new SampleTask("Task 2"));
	tm.joinAll();
	return 0;
}

compile

$ g++ task.cpp -I ~/git/poco/install/include -L ~/git/poco/install/lib -lPocoFoundationd -lpthread -std=c++11

output

$ ./a.out 
Task 1 progress: 0.1
Task 2 progress: 0.19
……
Task 2 progress: 0.83
Task 1 progress: 0.92
Task 1 finished.
Task 2 finished.

6.3 Poco::Activity activity object

6.3.1 What is an active object?

Active objects Active Objects are objects that run their member functions in their own thread.
In POCO, activity objects support two kinds of activity member functions:

  • Activity is a potentially long-running void/no parameter member function that runs in its own thread.
  • ActiveMethod is a non-null single-argument member function that runs in its own thread.
  • All activity methods can share a thread (in which case the calls will be queued), or each method can have its own thread.
  • Activities can be started automatically upon object construction or manually at a later time.
  • The event can be stopped at any time. To do this, the activity must periodically call the isStopped() member function.
  • Methods that implement activities cannot have parameters or return values.
  • Active threads are taken from the default thread pool

6.3.2 Example

If you don’t understand the above explanation, it doesn’t matter. Just look at the example and you will understand.
In the following example, put ActivityExample::runActivity() into the Poco::Activity activity object to run...

$ vi active.cpp

#include "Poco/Activity.h"
#include "Poco/Thread.h"
#include <iostream>

using Poco::Thread;

class ActivityExample
{
    
    
public:
		ActivityExample(): _activity(this, &ActivityExample::runActivity) {
    
    }
		void start()
		{
    
    
			_activity.start();
		}
		
		void stop()
		{
    
    
			_activity.stop(); // request stop
			_activity.wait(); // wait until activity actually stops
		}
		
protected:
		void runActivity()
		{
    
    
			while (!_activity.isStopped())
			{
    
    
				std::cout << "Activity running." << std::endl;
				Thread::sleep(200);
			} 
		}
private:
		Poco::Activity<ActivityExample> _activity;
};

int main(int argc, char** argv)
{
    
    
	ActivityExample example;
	example.start();
	Thread::sleep(2000);
	example.stop();
	return 0;
}

Compile:

$ g++ active.cpp -I ~/git/poco/install/include -L ~/git/poco/install/lib -lPocoFoundationd -lpthread -std=c++11

Output:

~/test/poco$ ./a.out 
Activity running.
……
Activity running.

6.4 Poco::ActiveMethod activity method

6.4.1 What are activity methods?

Activity methods are member functions that execute in their own thread (taken from the default thread pool).
Activity methods can share a thread. In this case, only one activity method can be executed at a time while other activity methods are waiting in the queue for execution.
Activity methods accept only one parameter and return a value.
To pass multiple parameters, you can use struct, std::pair or Poco::Tuple.
The return value of the activity method is stored in Poco::ActiveResult (also called Future).

6.4.2 Usage

Because the return value of an active method is not available immediately after calling the method (because the method runs in parallel), ActiveResult is used to deliver the result.
Poco::ActiveResult is a class template that is instantiated for the return type of a function.
Starting an activity method will return a Poco::ActiveResult that contains the result (or exception).
Use wait() or tryWait() to wait for the result, and then get the result by calling data().

6.4.3 Example

$ vi activeMethod.cpp

#include "Poco/ActiveMethod.h"
#include "Poco/ActiveResult.h"
#include <utility>
#include <iostream>

using Poco::ActiveMethod;
using Poco::ActiveResult;
class ActiveAdder
{
    
    
public:
	ActiveAdder():add(this, &ActiveAdder::addImpl){
    
    }
	ActiveMethod<int, std::pair<int, int>, ActiveAdder> add;

private:
	int addImpl(const std::pair<int, int>& args)
	{
    
    
		return args.first + args.second;
	}
};

int main(int argc, char** argv)
{
    
    
	ActiveAdder adder;
	ActiveResult<int> sum = adder.add(std::make_pair(1, 2));
	sum.wait();
	std::cout << sum.data() << std::endl;
	return 0;
}

Compile:

$ g++ activeMethod.cpp -I ~/git/poco/install/include -L ~/git/poco/install/lib -lPocoFoundationd -lpthread -std=c++11

6.5 Poco::ActiveDispatcher activity dispatch

6.5.1 What is activity scheduling?

The default behavior of Poco::ActiveMethod does not conform to the "classic" definition of an active object, where methods are queued for execution in a single thread.
To get classic behavior, you can use Poco::ActiveDispatcher as the base class for active objects.
You need to tell Poco::ActiveMethod to use Poco::ActiveDispatcher to schedule the execution of the method.

6.5.2 Example

$ we activeAdder.cpp

#include "Poco/ActiveMethod.h"
#include "Poco/ActiveResult.h"
#include "Poco/ActiveDispatcher.h"
#include <utility>
#include <iostream>

using Poco::ActiveMethod;
using Poco::ActiveResult;

class ActiveAdder: public Poco::ActiveDispatcher
{
    
    
public:
	ActiveAdder(): add(this, &ActiveAdder::addImpl){
    
    }
	ActiveMethod<int, std::pair<int, int>, ActiveAdder,
		Poco::ActiveStarter<Poco::ActiveDispatcher> > add;
private:
	int addImpl(const std::pair<int, int>& args)
	{
    
    
		return args.first + args.second;
	}
};

int main(int argc, char** argv)
{
    
    
	ActiveAdder adder;
	ActiveResult<int> sum = adder.add(std::make_pair(1, 2));
	sum.wait();
	std::cout << sum.data() << std::endl;
	return 0;
}

Compile:

$ g++ activeAdder.cpp -I ~/git/poco/install/include -L ~/git/poco/install/lib -lPocoFoundationd -lpthread -std=c++11

Guess you like

Origin blog.csdn.net/u010168781/article/details/134654363