При написании кода в последнее время блокировка чтения-записи rwlock используется при работе с синхронизацией данных между несколькими потоками. В многопоточной синхронизации чаще используется mutex mutex В чем разница, плюсы и минусы между rwlock и mutex?
Во-первых, распространенное заблуждение заключается в том, что rwlock должен работать лучше, чем мьютекс, в случае большего количества операций чтения и меньшего количества операций записи. На самом деле, поскольку rwlock различает блокировки чтения и записи, для каждой блокировки требуется дополнительная логическая обработка (например, различение блокировок чтения и блокировок записи, предотвращение «голодания» блокировок записи и т. д.) Меньше, чем более простой мьютекс; однако , rwlock фактически улучшает параллелизм благодаря повторным блокировкам чтения, уменьшая задержку в случае большего количества операций чтения и меньшего количества операций записи.
Мы можем провести следующие эксперименты для проверки:
#include <pthread.h>
#include <iostream>
#include <unistd.h>
pthread_mutex_t mutex;
int i = 0;
void *thread_func(void* args) {
int j;
for(j=0; j<10000000; j++) {
pthread_mutex_lock(&mutex);
for(int k=0; k<1; k++) {
int t = i;
t++;
}
pthread_mutex_unlock(&mutex);
}
pthread_exit((void *)0);
}
int main(void) {
pthread_t id1;
pthread_t id2;
pthread_t id3;
pthread_t id4;
pthread_mutex_init(&mutex, NULL);
pthread_create(&id1, NULL, thread_func, (void *)0);
pthread_create(&id2, NULL, thread_func, (void *)0);
pthread_create(&id3, NULL, thread_func, (void *)0);
pthread_create(&id4, NULL, thread_func, (void *)0);
pthread_join(id1, NULL);
pthread_join(id2, NULL);
pthread_join(id3, NULL);
pthread_join(id4, NULL);
pthread_mutex_destroy(&mutex);
}
#include <pthread.h>
#include <iostream>
#include <unistd.h>
pthread_rwlock_t rwlock;
int i = 0;
void *thread_func(void* args) {
int j;
for(j=0; j<10000000; j++) {
pthread_rwlock_rdlock(&rwlock);
for(int k=0; k<1; k++) {
int t = i;
t++;
}
pthread_rwlock_unlock(&rwlock);
}
pthread_exit((void *)0);
}
int main(void) {
pthread_t id1;
pthread_t id2;
pthread_t id3;
pthread_t id4;
pthread_rwlock_init(&rwlock, NULL);
pthread_create(&id1, NULL, thread_func, (void *)0);
pthread_create(&id2, NULL, thread_func, (void *)0);
pthread_create(&id3, NULL, thread_func, (void *)0);
pthread_create(&id4, NULL, thread_func, (void *)0);
pthread_join(id1, NULL);
pthread_join(id2, NULL);
pthread_join(id3, NULL);
pthread_join(id4, NULL);
pthread_rwlock_destroy(&rwlock);
}
Видно, что в этих двух случаях логика вычислений в принципе отсутствует, а то, что делает поток, постоянно блокируется и разблокируется.
Производительность мьютекса:
real 0m2.363s
user 0m1.904s
sys 0m3.592s
производительность блокировки:
real 0m5.157s
user 0m5.932s
sys 0m10.660s
Видно, что mutex лучше rwlock с точки зрения производительности блокировки.
Это просто идеальная ситуация: в нормальных условиях в критической секции часто необходимо выполнять некоторые операции вычисления/ввода для общих ресурсов. Мы изменили внешний цикл и внутренний цикл в приведенном выше коде на 1000 раз и 10000 раз соответственно, чтобы смоделировать ситуацию с определенным объемом вычислений. Результаты теста следующие:
Производительность мьютекса:
real 0m0.102s
user 0m0.024s
sys 0m0.088s
производительность блокировки:
real 0m0.045s
user 0m0.112s
sys 0m0.012s
Обратите внимание, что с реальной точки зрения rwlock уже лучше мьютекса. Кроме того, для мьютекса user+sys в основном равно реальному, что показывает, что он в принципе не приносит параллелизма; в то время как пользовательское время rwlock больше, чем реальное, можно видеть, что код части внутреннего цикла вызван определенный параллелизм.
Но на этот раз наблюдайте использование процессоров, в основном все загружены.
После завершения внутреннего цикла мы используем usleep(1000) для имитации периода ожидания ввода-вывода. Результаты испытаний в этом случае следующие:
Производительность мьютекса:
real 0m7.987s
user 0m0.200s
sys 0m0.412s
производительность блокировки:
real 0m1.632s
user 0m0.112s
sys 0m0.028s
Можно видеть, что в это время rwlock работает лучше, а повторный вход полностью использует время, которое потоки ожидают ввода-вывода, чтобы улучшить параллелизм.
Приведенные выше примеры на самом деле предназначены для иллюстрации того, что в этом случае лучший способ — выполнить тест производительности для бизнес-сценария и использовать фактические результаты теста в качестве критерия для выбора используемой блокировки.
Однако у rwlock есть очень большая скрытая опасность, которая также вызвана повторным входом блокировки чтения: предварительным условием повторного входа блокировки чтения является то, что он находится в критической области, контролируемой блокировкой чтения.На общем ресурсе есть только операции чтения и нет операций записи.. Однако сопровождающим программы (не разработчикам) легко это не заметить (подумайте о том, что вы берете на себя часть кода, написанного кем-то другим, и уделяете особое внимание тому, контролируется ли определенная часть кода rwlock или мьютексом? ), тем самым вводя операции записи в область действия блокировки чтения. Я думаю, что это одна из самых серьезных проблем, которую следует учитывать при использовании блокировок чтения-записи.
Рекомендуемое чтение:
Проблема с сопоставлением шаблонов scala
Пройти Python и C++
Безработная молодежь
Пожалуйста, укажите источник:blog.mandarin.com/2018/02/11/...
Добро пожаловать в WeChat для сканирования приведенного ниже QR-кода. Подпишитесь на мою общедоступную учетную запись WeChat TechTalking, Technology·Life·Thinking:
Бэкенд-технологии «черный дом»