Non-real read/write locks, non-real read/write locks
There is a single write thread and multiple read threads are concurrently used. For example, when reading and updating measurement data, there are many consumers and there is only one producer. Example:
The left side is a classic solution that locks the entire data operation. It is a waste of time to lock all read threads for a Data Writing thread. Therefore, the Reader/Writer Lock is proposed. Even if a read/write Lock is used, its essence is the same, and the internal implementation of pthread under POSIX is based on mutex, therefore, it has higher overhead. If there is no heavy read operation to offset the overhead it introduces, it will lead to performance degradation. Multiple groups of test data have been used to prove this. I did the verification myself and got the following data (a single write thread, 20 read threads). Using the read/write lock is slower than using mutex. For details, refer to the two links:
* Mutex or Reader Writer Lock
* Multi-threaded programming: efficiency of locking
This type of problem has a type of solution in the database field, called Multiversion Concurrency Control, which aims to add data copies to ensure that users can use complete data every time they use it, but not necessarily the latest data. To simplify it, the idea is to create a copy of data for writing. When the data is fully prepared, it is switched out for other threads to read. The original data is converted to the next write operation. That is, the method shown on the right.
In this solution, you only need to lock the processing of Writing/Reading. In this way, the performance overhead is tested because the Lock processing time is very short, which is better than Mutex and Reader/Writer Lock (the last algorithm ):
The details are not expanded. In addition, there are some more common methods called Spin Buffer, which can be further studied if you are interested.
The source code is as follows:
#include <pthread.h>#include <iostream>#include <stdio.h>#include <unistd.h>#include <ctime>// #define USE_MUTEX// #define USE_RW_LOCK// X + Y = 0typedef struct _Data{ int x; int y;} Data;namespace { Data globalData[2] = {{1,-1}, {1,-1}}; int WriteIndex = 0; int ReadingIndex = 1; float globalReadingTimeCost = 0.0f;#ifdef USE_MUTEX pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;#endif#ifdef USE_RW_LOCK pthread_rwlock_t rwlock;#endif const int thread_number = 20;}void* write_thread(void* param) { clock_t begin_time = std::clock(); for(int i=1; i<=1000; i++) { globalData[WriteIndex].x = i; globalData[WriteIndex].y = -1 * i; usleep(1);#ifdef USE_MUTEX pthread_mutex_lock(&mutex);#endif#ifdef USE_RW_LOCK pthread_rwlock_rdlock(&rwlock);#endif ReadingIndex = WriteIndex; WriteIndex = (WriteIndex + 1) % 2;#ifdef USE_MUTEX pthread_mutex_unlock(&mutex);#endif#ifdef USE_RW_LOCK pthread_rwlock_unlock(&rwlock);#endif usleep(600); } std::cout<< "[Writing Thread]" << float( std::clock () - begin_time ) / CLOCKS_PER_SEC * 1000 << std::endl; return NULL;}void* read_thread(void* param) { clock_t begin_time = std::clock(); for(int i=1; i<=20000; i++) {#ifdef USE_MUTEX pthread_mutex_lock(&mutex);#endif#ifdef USE_RW_LOCK pthread_rwlock_wrlock(&rwlock);#endif int index = ReadingIndex;#ifdef USE_MUTEX pthread_mutex_unlock(&mutex);#endif#ifdef USE_RW_LOCK pthread_rwlock_unlock(&rwlock);#endif int x = globalData[index].x; int y = globalData[index].y; if (x + y != 0) { std::cout << std::endl << "Wrong data:" << x << "," << y << std::endl; } usleep(3); } std::cout<< "[Reading Thread]" << float( std::clock () - begin_time ) / CLOCKS_PER_SEC * 1000 << std::endl; return NULL;}int main(void) { int ret = 0; pthread_t id[thread_number];#ifdef USE_RW_LOCK pthread_rwlock_init(&rwlock, NULL);#endif clock_t begin_time = std::clock(); // One writing thread ret = pthread_create(&id[0], NULL, write_thread, NULL); if (ret) { std::cout << "Failed to launch writing thread." << std::endl; return -1; } // Four reading threads for (int i=1; i<thread_number; i++) { pthread_create(&id[i], NULL, read_thread, NULL); } for (int i=0; i<=thread_number; i++) { pthread_join(id[i], NULL); } std::cout<< "Cost:" << float( std::clock () - begin_time ) / CLOCKS_PER_SEC * 1000 << std::endl; return 0;}
Compile the test as follows:
g++ -std=c++11 -DUSE_MUTEX thread.cc -lpthread -o thread
If you have time, write another article about multithreading algorithm selection!
Copyright Disclaimer: This article is an original article by the blogger and cannot be reproduced without the permission of the blogger.