Должна ли производительность блокировок чтения-записи быть лучше?

Linux C++
Должна ли производительность блокировок чтения-записи быть лучше?

При написании кода в последнее время блокировка чтения-записи 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:
后端技术小黑屋Бэкенд-технологии «черный дом»