Provides a mechanism that synchronizes access to objects.
- Inheritance
- xtd::static_object → xtd::threading::monitor
- Header
#include <xtd/threading/monitor>
- Namespace
- xtd::threading
- Library
- xtd.core
- Examples
- The following example uses the xtd::threading::monitor class to synchronize access to a single instance of a random number generator represented by the xtd::random class. The example creates ten threads, each of which executes asynchronously on a thread pool thread. Each thread generates 10,000 random numbers, calculates their average, and updates two procedure-level variables that maintain a running total of the number of random numbers generated and their sum. After all threads have executed, these two values are then used to calculate the overall mean.
#include <xtd/threading/interlocked>
#include <xtd/threading/monitor>
#include <xtd/threading/thread>
#include <xtd/console>
#include <xtd/literals>
#include <xtd/random>
#include <xtd/startup>
#include <array>
#include <vector>
namespace monitor_example {
class program {
public:
static void main() {
auto threads = std::vector<thread> {};
auto total = 0_s64;
auto n = 0;
for (auto thread_ctr = 0; thread_ctr < 10; ++thread_ctr)
auto values = std::array<int, 10000> {};
auto thread_total = 0;
auto thread_n = 0;
auto ctr = 0;
for (ctr = 0; ctr < 10000; ++ctr)
values[ctr] = rnd.next(0, 1001);
thread_n = ctr;
for (auto value : values)
thread_total += value;
(thread_total * 1.0) / thread_n, thread_n);
}));
try {
for (auto& thread : threads)
(total * 1.0)/n, n);
}
}
}
};
}
startup_(monitor_example::program::main);
Because they can be accessed from any task running on a thread pool thread, access to the variables total and n must also be synchronized. The xtd::threading::interlocked::add method is used for this purpose.
The following example demonstrates the combined use of the xtd::threading::monitor class (implemented with the lock_ keyword), the xtd::threading::interlocked class, and the xtd::threading::auto_reset_event class. It defines two classes, sync_resource and un_sync_resource, that respectively provide synchronized and unsynchronized access to a resource. To ensure that the example illustrates the difference between the synchronized and unsynchronized access (which could be the case if each method call completes rapidly), the method includes a random delay: for threads whose xtd::threading::thread::managed_thread_id property is even, the method calls xtd::threading::thread::sleep to introduce a delay of 2,000 milliseconds. #include <xtd/threading/auto_reset_event>
#include <xtd/threading/interlocked>
#include <xtd/threading/thread>
#include <xtd/threading/thread_pool>
#include <xtd/console>
#include <xtd/lock>
#include <xtd/startup>
namespace monitor_lock_example {
class sync_resource :
public object {
public:
void access() {
}
}
};
class un_sync_resource :
public object {
public:
void access() {
}
};
class app {
private:
inline static int num_ops = 0;
inline static sync_resource sync_res;
inline static un_sync_resource un_sync_res;
public:
static void main() {
num_ops = 5;
for (int ctr = 0; ctr <= 4; ++ctr)
ops_are_done.wait_one();
num_ops = 5;
for (int ctr = 0; ctr <= 4; ctr++)
ops_are_done.wait_one();
}
static void sync_update_resource(
std::any state) {
sync_res.access();
ops_are_done.set();
}
static void un_sync_update_resource(
std::any state) {
un_sync_res.access();
ops_are_done.set();
}
};
}
startup_(monitor_lock_example::app::main);
The example defines a variable, num_ops, that defines the number of threads that will attempt to access the resource. The application thread calls the xtd::threading::thread_pool::queue_user_work_item method for synchronized and unsynchronized access five times each. The xtd::threading::thread_pool::queue_user_work_item method has a single parameter, a delegate that accepts no parameters and returns no value. For synchronized access, it invokes the sync_update_resource method; for unsynchronized access, it invokes the un_sync_update_resource method. After each set of method calls, the application thread calls the xtd::thrrading::auto_reset_event::wait_one method so that it blocks until the xtd::threading::auto_reset_event instance is signaled.
Each call to the sync_update_resource method calls the sync_resource::access method and then calls the xtd::threading::interlocked::decrement method to decrement the num_ops counter. The xtd::threading::interlocked::decrement method Is used to decrement the counter, because otherwise you cannot be certain that a second thread will access the value before a first thread's decremented value has been stored in the variable. When the last synchronized worker thread decrements the counter to zero, indicating that all synchronized threads have completed accessing the resource, the syncUpdate_resource method calls the xtd::threading::event_wait_handle::set method, which signals the main thread to continue execution.
Each call to the un_sync_update_resource method calls the un_syncResource::access method and then calls the xtd::threading::interlocked::decrement method to decrement the num_ops counter. Once again, the xtd::threading::interlocked::decrement method Is used to decrement the counter to ensure that a second thread does not access the value before a first thread's decremented value has been assigned to the variable. When the last unsynchronized worker thread decrements the counter to zero, indicating that no more unsynchronized threads need to access the resource, the un_sync_update_resource method calls the xtd::threading::event_wait_handle::set method, which signals the main thread to continue execution.
As the output from the example shows, synchronized access ensures that the calling thread exits the protected resource before another thread can access it; each thread waits on its predecessor. On the other hand, without the lock, the un_sync_resource::access method is called in the order in which threads reach it.
- The xtd::threading::monitor class: An overview
- xtd::threading::monotor has the following features:
- It is associated with an object on demand.
- It is unbound, which means it can be called directly from any context.
- An instance of the xtd::threading::monitor class cannot be created; the methods of the xtd::threading::monitor class are all static. Each method is passed the synchronized object that controls access to the critical section. The following table describes the actions that can be taken by threads that access synchronized objects:
Action | Description |
xtd::threading::monitor::enter, xtd::threading::monitor::try_enter | Acquires a lock for an object. This action also marks the beginning of a critical section. No other thread can enter the critical section unless it is executing the instructions in the critical section using a different locked object. |
xtd::threading::monitor::wait | Releases the lock on an object in order to permit other threads to lock and access the object. The calling thread waits while another thread accesses the object. xtd::threading::monitor::pulse signals are used to notify waiting threads about changes to an object's state. |
xtd::threading::monitor::pulse, xtd::threading::monitor::pulse_all | Sends a signal to one or more waiting threads. The signal notifies a waiting thread that the state of the locked object has changed, and the owner of the lock is ready to release the lock. The waiting thread is placed in the object's ready queue so that it might eventually receive the lock for the object. Once the thread has the lock, it can check the new state of the object to see if the required state has been reached. |
xtd::threading::monitor::exit | Releases the lock on an object. This action also marks the end of a critical section protected by the locked object. |
There are two sets of overloads for the xtd::threading::monitor::enter and xtd::threading::monitor::try_enter methods. One set of overloads has a boolean parameter that is atomically set to true if the lock is acquired, even if an exception is thrown when acquiring the lock. Use these overloads if it is critical to release the lock in all cases, even when the resources the lock is protecting might not be in a consistent state.
- The lock object
- The xtd::threading::monitor class consists of static methods that operate on an object that controls access to the critical section. The following information is maintained for each synchronized object:
- A reference to the thread that currently holds the lock.
- A reference to a ready queue, which contains the threads that are ready to obtain the lock.
- A reference to a waiting queue, which contains the threads that are waiting for notification of a change in the state of the locked object.
- The critical section
- Use the xtd::threading::monitor::enter and xtd::threading::monitor::exit methods to mark the beginning and end of a critical section.
- Note
- The functionality provided by the xtd::threading::monitor::enter and xtd::threading::monitor::exit methods is identical to that provided by the xtd::threading::lock_guard object and the lock_ keyword, except that with the xtd class: :threading::lock_guard class and the lock_ keyword, the xtd::threading::monitor::enter method and the xtd::threading::monitor::exit method are always called, even if an exception has occurred.
-
It is therefore advisable to use the xtd::threading::lock_guard class or the lock_ keyword instead of calling the xtd::threading::monitor::enter and xtd::threading::monitor::exit methods, to ensure that no critical sections remain in the xtd::threading::monitor class.
- The folowing code shows the use use of xtd:threading::monitor::enter and xtd:threading::monitor::exit when an exception occured :
auto obj = object {};
try {
} catch(...) {
}
The same code with the xtd::threading::lock_guard class : auto obj = object {};
try {
} catch(...) {
}
The same code with the lock_ keyword : auto obj = object {};
try {
}
} catch(...) {
}
- pulse, pulse_all, and wait
- Once a thread owns the lock and has entered the critical section that the lock protects, it can call the xtd::threading::monitor::wait, xtd::threading::monitor::pulse, and xtd::threading::monitor::pulse_all methods.
When the thread that holds the lock calls xtd::threading::monitor::wait, the lock is released and the thread is added to the waiting queue of the synchronized object. The first thread in the ready queue, if any, acquires the lock and enters the critical section. The thread that called Wait is moved from the waiting queue to the ready queue when either the xtd::threading::monitor::pulse or the xtd::threading::monitor::pulse_all method is called by the thread that holds the lock (to be moved, the thread must be at the head of the waiting queue). The xtd::threading::monitor::wait method returns when the calling thread reacquires the lock.
When the thread that holds the lock calls xtd::threading::monitor::pulse, the thread at the head of the waiting queue is moved to the ready queue. The call to the xtd::threading::monitor::pulse_all method moves all the threads from the waiting queue to the ready queue.
- Monitors and wait handles
- It is important to note the distinction between the use of the xtd::threading::monitor class and xtd::threading::wait_handle objects.
- The xtd::threading::monitor class is purely managed, fully portable, and might be more efficient in terms of operating-system resource requirements.
- xtd::threading::wait_handle objects represent operating-system waitable objects, are useful for synchronizing between managed and unmanaged code, and expose some advanced operating-system features like the ability to wait on many objects at once.
|
template<typename object_t > |
static void | enter (const object_t &obj) |
| Acquires an exclusive lock on the specified obj. More...
|
|
template<typename object_t > |
static void | enter (const object_t &obj, bool &lock_taken) |
| Acquires an exclusive lock on the specified obj. More...
|
|
template<typename object_t > |
static void | exit (const object_t &obj) |
| Releases an exclusive lock on the specified obj. More...
|
|
template<typename object_t > |
static bool | is_entered (const object_t &obj) |
| Determines whether the current thread holds the lock on the specified object. More...
|
|
template<typename object_t > |
static void | pulse (const object_t &obj) |
| Notifies a thread in the waiting queue of a change in the locked object's state. More...
|
|
template<typename object_t > |
static void | pulse_all (const object_t &obj) |
| Notifies all waiting threads of a change in the object's state. More...
|
|
template<typename object_t > |
static bool | try_enter (const object_t &obj) noexcept |
| Attempts to acquire an exclusive lock on the specified object. More...
|
|
template<typename object_t > |
static bool | try_enter (const object_t &obj, bool &lock_taken) noexcept |
| Attempts to acquire an exclusive lock on the specified object. More...
|
|
template<typename object_t > |
static bool | try_enter (const object_t &obj, int32 milliseconds_timeout) noexcept |
| Attempts, for the specified number of milliseconds, to acquire an exclusive lock on the specified object. More...
|
|
template<typename object_t > |
static bool | try_enter (const object_t &obj, int32 milliseconds_timeout, bool &lock_taken) noexcept |
| Attempts, for the specified number of milliseconds, to acquire an exclusive lock on the specified object. More...
|
|
template<typename object_t > |
static bool | try_enter (const object_t &obj, int64 milliseconds_timeout) noexcept |
| Attempts, for the specified number of milliseconds, to acquire an exclusive lock on the specified object. More...
|
|
template<typename object_t > |
static bool | try_enter (const object_t &obj, int64 milliseconds_timeout, bool &lock_taken) noexcept |
| Attempts, for the specified number of milliseconds, to acquire an exclusive lock on the specified object. More...
|
|
template<typename object_t > |
static bool | try_enter (const object_t &obj, const time_span &timeout) noexcept |
| Attempts, for the specified amount of time, to acquire an exclusive lock on the specified object. More...
|
|
template<typename object_t > |
static bool | try_enter (const object_t &obj, const time_span &timeout, bool &lock_taken) noexcept |
| Attempts, for the specified amount of time, to acquire an exclusive lock on the specified object. More...
|
|
template<typename object_t > |
static bool | wait (const object_t &obj, int32 milliseconds_timeout) |
| Releases the lock on an object and blocks the current thread until it reacquires the lock. If the specified time-out interval elapses, the thread enters the ready queue. More...
|
|
template<typename object_t > |
static bool | wait (const object_t &obj, const time_span &timeout) |
| Releases the lock on an object and blocks the current thread until it reacquires the lock. If the specified time-out interval elapses, the thread enters the ready queue. More...
|
|
template<typename object_t > |
static bool | wait (const object_t &obj) |
| Releases the lock on an object and blocks the current thread until it reacquires the lock. More...
|
|