Продолжим тему использования механизма RCU в системном программировании Linux. В первой части мы создали примитивный цикл неблокирующего изменения одной переменной, в данной части  разберем пример неблокирующего изменения  списка значений. В тесте используем библиотеку liburcu. Данная библиотека предоставляет как примитивы RCU так и работу с полноценными структурами, такими как  хеш-таблицы, очереди, стеки и  списки.  Подробное описание https://liburcu.org/ Для нашего сегодняшнего теста выбрана работа с динамическим RCU списком.  https://lwn.net/Articles/573441/.   Поскольку основной смысл применения  RCU  это задачи с одним писателем и множеством читателей, попробуем  реализовать следующий пример. Дан динамический список t_list в который необходимо добавлять и удалять записи, без  блокировок чтения и нарушения целостности данных. Каждый элемент списка будет указывать на структуру node  из структуры  t_node в которой поля node и rcu_head необходимы для работы RCU, а value является пользовательскими данными. Для работы со списком  формируем  пул потоков из которых, один является писателем и определяется функцией void *test_l_add(void *arg) , а остальные будут читателями с функцией void *test_l_get(void *arg).  После запуска нитей, основной поток ожидает 5 секунд, разблокирует Mutex остановки и ждет завершения всех дочерних нитей. Затем освобождение памяти и завершение процесса. Функция писателя void *test_l_add(void *arg) выполняет бесконечный цикл в ожидании освобождения Mutex. В цикле выполняется  имитация работы со списком, при каждой итерации цикла добавляются 2 элемента   списка со значениями REZ1 и REZ2.  Функция  cds_list_add_tail_rcu выполняется вне секции rcu_read_lock и не блокирует читателей. Каждый читатель, пока находится в rcu_read_lock, будет видеть старую версию списка. На каждый сотый цикл выполняется поиск и удаление всех элементов со значением REZ2. В данном фрагменте реализован механизм отложенного удаления ресурсов, после вызова функции cds_list_del_rcu  вызывается функция call_rcu которой передается в качестве параметра имя функции для освобождения ресурсов.   Функция освобождения ресурсов , вызывается в момент когда все нити  читатели данного ресурса покинули секцию rcu_read_lock. Это очень важный момент, мы не вызываем synchronize_rcu() а поручаем библиотеке освободить ресурс когда это станет возможным, тем самым  не блокируем писателя.  Функция читателя void *test_l_get(void *arg) , также в бесконечном цикле  выполняет чтение всего списка с проверкой корректности значений value. Чтение списка происходит в в секции rcu_read_lock(). Макрос cds_list_for_each_entry_rcu выполняет просмотр всего списка, возвращая указатель на структуру t_node. При освобождении Mutex нить завершает работу и выводит на экран количество циклов чтения и количество считанных элементов  в последнем цикле.  Необходимо обратить внимание что rcu_read_lock() и rcu_read_unlock() всегда парные , не допускайте  незакрытую секцию чтения. Приступаем к тестированию основной задачи - показать что тестовое приложение работает эффективно в независимости от числа нитей читателей. Первый запуск выполним для проверки холостой работы писателя. Установим количество нитей = 1.  Результатом работы являются строка Start и End , которые показывают номер нити и количество циклов. List empty выдает информацию сколько в нашем списке значений value равно REZ1 и REZ2 соответственно. Результаты постоянно изменяются +/- тысяча при 350 тысячах циклов за 5 секунд это нормально. Итак примем что моих условиях одна нить совершает ~340-360 тысяч циклов. Start ADD :0 End ADD :0 :356500 List empty 356500 1 Писателям без читателей скучно, добавим 1. Появилась запись End GET читатель выполнил 3947 циклов и в последнем цикле в списке было  359499 элементов. Start ADD :0 Start GET :1 End GET :1 :3947 :359499 End ADD :0 :359440 List empty 359440 41 Добавим сразу до 4.  Start ADD :0 Start GET :2 Start GET :1 Start GET :3 Start GET :4 End GET :1 :4567 :364999 End ADD :0 :364977 End GET :4 :3669 :365055 End GET :2 :4175 :365055 End GET :3 :3794 :365055 List empty 364977 78 И до 12, больше чем количество ядер особого смысла нет.   Start ADD :0 Start GET :3 Start GET :2 Start GET :1 Start GET :4 Start GET :11 Start GET :10 Start GET :5 Start GET :7 Start GET :9 Start GET :6 Start GET :8 End GET :10 :4101 :387799 End GET :1 :4414 :387899 End GET :6 :3699 :387999 End GET :2 :4140 :387999 End ADD :0 :387974 End GET :3 :4957 :388049 End GET :4 :3901 :388049 End GET :5 :3550 :388049 End GET :7 :3999 :388049 End GET :11 :3917 :388049 End GET :8 :3637 :388049 End GET :9 :3755 :388049 List empty 387974 75 По результатам прошу обратить внимание что каждая из нитей останавливается со своим значением количество элементов, до тех пор пока не остановилась нить-писатель, далее все читатели считали зафиксированный результат 387974+75 и остановились.   И каков итог измерений?  RCU работает и очень эффективно, важно только правильно применить.  Вполне уверенно можно сказать что тестовый проект получился  рабочий, эффективный и  простой в понимании. Надеюсь данный материал будет полезен людям, изучающим системное программирование в Linux. Код проекта https://github.com/vic-35/test_liburcu2 , проект создан в QT creator для облегчения отладки и запуска.  Программист C/С++