Although boost: mutex provides the lock and try_lock methods, boost: timed_mutex only supports timed_lock, which is the reason why the above example is used. If timed_lock is not required, boost: mutex can be used as in the previous example.
Like boost: lock_guard, the boost: unique_lock destructor also release mutex. In addition, you can manually use unlock () to release mutex. You can also call release () to unassociate boost: unique_lock from mutex, as in the preceding example. However, in this case, the unlock () method must be explicitly called to release mutex, because the boost: unique_lock destructor no longer do this.
Boost: unique_lock the so-called exclusive lock means that a mutex can only be obtained by one thread at the same time. Other threads must wait until the mutex is released again. In addition to exclusive locks, there are also non-exclusive locks. A boost: shared_lock class in boost. Thread provides a non-exclusive lock. As in the following example, this class must be used together with the mutex of the boost: shared_mutex type.
#include <boost/thread.hpp> #include <iostream> #include <vector> #include <cstdlib> #include <ctime> void wait(int seconds) { boost::this_thread::sleep(boost::posix_time::seconds(seconds)); } boost::shared_mutex mutex; std::vector<int> random_numbers; void fill() { std::srand(static_cast<unsigned int>(std::time(0))); for (int i = 0; i < 3; ++i) { boost::unique_lock<boost::shared_mutex> lock(mutex); random_numbers.push_back(std::rand()); lock.unlock(); wait(1); } } void print() { for (int i = 0; i < 3; ++i) { wait(1); boost::shared_lock<boost::shared_mutex> lock(mutex); std::cout << random_numbers.back() << std::endl; } } int sum = 0; void count() { for (int i = 0; i < 3; ++i) { wait(1); boost::shared_lock<boost::shared_mutex> lock(mutex); sum += random_numbers.back(); } } int main() { boost::thread t1(fill); boost::thread t2(print); boost::thread t3(count); t1.join(); t2.join(); t3.join(); std::cout << "Sum: " << sum << std::endl; }
Boost: a non-exclusive lock of the shared_lock type can be used when the thread only reads a certain resource. A resource modified by a thread requires write access, so an exclusive lock is required. This is also obvious: the thread that only needs read access does not need to know whether other threads are accessed at the same time. Therefore, a non-exclusive lock can share a mutex.
In the given example, print () and count () can both read-only access random_numbers. Although the print () function writes the last number in random_numbers to the standard output, the count () function counts it into the sum variable. Since there is no function to modify random_numbers, all can access it with a non-exclusive lock of the boost: shared_lock type at the same time.
In the fill () function, a non-exclusive lock of the boost: unique_lock type is required because it inserts a new random number into random_numbers. After unlock () explicitly calls unlock () to release mutex, fill () waits for one second. Compared to the previous one, wait () is called at the end of the for loop to ensure that at least one random number exists in the container and can be accessed by print () or count.
Correspondingly, the two functions call wait () at the beginning of the for loop ().
Considering that wait () is called separately in different places, a potential problem becomes obvious: the order of function calls is directly determined by the order in which each independent process is executed by the CPU. The so-called conditional variables can be used to synchronize independent threads so that each element of the array is immediately added to random_numbers by different threads.
#include <boost/thread.hpp> #include <iostream> #include <vector> #include <cstdlib> #include <ctime> boost::mutex mutex; boost::condition_variable_any cond; std::vector<int> random_numbers; void fill() { std::srand(static_cast<unsigned int>(std::time(0))); for (int i = 0; i < 3; ++i) { boost::unique_lock<boost::mutex> lock(mutex); random_numbers.push_back(std::rand()); cond.notify_all(); cond.wait(mutex); } } void print() { std::size_t next_size = 1; for (int i = 0; i < 3; ++i) { boost::unique_lock<boost::mutex> lock(mutex); while (random_numbers.size() != next_size) cond.wait(mutex); std::cout << random_numbers.back() << std::endl; ++next_size; cond.notify_all(); } } int main() { boost::thread t1(fill); boost::thread t2(print); t1.join(); t2.join(); }
In this example, the program deletes wait () and count (). The thread does not have to wait for one second in each loop iteration, but is executed as quickly as possible. In addition, the total amount is not calculated; the number is completely written to the standard output stream.
To ensure correct processing of random numbers, a condition variable that allows checking specific conditions between multiple threads is required to synchronize different independent threads.
As mentioned above, the fill () function generates a random number for each iteration and then
Random_numbersContainer. To prevent other threads from simultaneously accessing the container, an exclusive lock is required. This example uses a conditional variable instead of waiting for one second. Calling yy_all () will wake up every thread that is waiting for this notification by calling wait.
By viewing the for loop in the print () function, we can see that the same condition variable is called by the wait () function. If this thread is awakened by yy_all (), it will try to obtain the mutex, but it will succeed only after the fill () function is completely released.
The trick here is that calling wait () will release the mutex passed by parameters. After notify_all () is called, The fill () function releases the thread through wait. It then blocks and waits for other threads to call yy_all (). Once the random number has been written to the standard output stream, this will happen in print.
Note that calling wait () in the print () function actually happens in a separate while loop. The purpose of this operation is to process the random number that has been placed in the container before the wait () function is called for the first time in the print () function. By comparing the number and expected value of elements in random_numbers, it is found that the random number is successfully written to the standard output stream.
4. Local thread storage
Thread Local Storage (TLS) is a dedicated storage area that can only be accessed by one thread. TLS variables can be seen as global variables visible only to a specific thread rather than the entire program. The following example shows the benefits of these variables.
#include <boost/thread.hpp> #include <iostream> #include <cstdlib> #include <ctime> void init_number_generator() { static bool done = false; if (!done) { done = true; std::srand(static_cast<unsigned int>(std::time(0))); } } boost::mutex mutex; void random_number_generator() { init_number_generator(); int i = std::rand(); boost::lock_guard<boost::mutex> lock(mutex); std::cout << i << std::endl; } int main() { boost::thread t[3]; for (int i = 0; i < 3; ++i) t[i] = boost::thread(random_number_generator); for (int i = 0; i < 3; ++i) t[i].join(); }
This example creates three threads, each of which writes a random number to the standard output stream. The random_number_generator () function creates a random number using the STD: rand () function defined in the C ++ standard. However, the random number generator used for STD: rand () must be correctly initialized with STD: srand. If not, the program always prints the same random number.
The random number generator returns the current time through STD: Time () and completes initialization in the init_number_generator () function. Because this value is different each time, it can be ensured that the generator always initializes with different values to generate different random numbers. Because the generator only needs to be initialized once, init_number_generator () uses a static variable done as the condition quantity.
If the program runs multiple times, the random number of the write 2/3 is obviously the same. In fact, this program has a defect: The generator used by STD: Rand must be initialized by various threads. Therefore, the implementation of init_number_generator () is actually incorrect, because it only calls STD: srand () once (). With TLS, this defect can be corrected.
#include <boost/thread.hpp> #include <iostream> #include <cstdlib> #include <ctime> void init_number_generator() { static boost::thread_specific_ptr<bool> tls; if (!tls.get()) tls.reset(new bool(false)); if (!*tls) { *tls = true; std::srand(static_cast<unsigned int>(std::time(0))); } } boost::mutex mutex; void random_number_generator() { init_number_generator(); int i = std::rand(); boost::lock_guard<boost::mutex> lock(mutex); std::cout << i << std::endl; } int main() { boost::thread t[3]; for (int i = 0; i < 3; ++i) t[i] = boost::thread(random_number_generator); for (int i = 0; i < 3; ++i) t[i].join(); }
Replace static done with a TLS variable. It is based on boost: thread_specific_ptr instantiated with the bool type. In principle, TLS works like done: it can be used as a condition to indicate whether the random number generator is initialized. But the key difference is that the value stored in TLS is visible and available only to the corresponding thread.
Once a boost: thread_specific_ptr variable is created, it can be set accordingly. However, it expects to get the address of a Boolean variable, not itself. You can use the reset () method to save its address to TLS. In the example given, a bool variable is dynamically allocated, whose address is returned by new and saved to TLS. To avoid setting TLS for every call to init_number_generator (), the get () function is used to check whether an address has been saved.
Because boost: thread_specific_ptr saves an address, it acts like a normal pointer. Therefore, operator * () and operator-> () are overloaded for ease of use. In this example, * TLS is used to check whether the current condition is true or false. Then, based on the current condition, the random number generator determines whether to initialize.
As you can see, boost: thread_specific_ptr allows the current process to save the address of an object, and then only allows the current process to get the address. However, when a thread has successfully saved this address, other threads may fail.
It may be surprising if the program is being executed: despite the TLS variable, the random numbers generated are still equal. This is because three threads are created at the same time, resulting in random number generator initialization at the same time. If the program executes several times, the random number will change, which indicates that the generator initialization is correct.