#c #multithreading #winapi #atomic #iocp
#c #многопоточность #winapi #атомарный #iocp
Вопрос:
В C у меня есть Stream
объект, который абстрагирует HANDLE
в Windows, и у меня также есть различные производные объекты, такие как File
, TcpSocket
, UdpSocket
, Pipe
который происходит непосредственно от этого Stream
объекта, а затем у меня также есть RequestIo
объект, который является моей собственной версией расширенного OVERLAPPED
объекта, то есть RequestIo
напрямую наследуется от OVERLAPPED
структуры. Отныне говорить RequesIo
— это то же самое, что говорить OVERLAPPED
.
В RequestIo
объекте я храню несколько полезных вещей, которые невозможно сохранить в единой OVERLAPPED
структуре, таких как флаги, указатели пользователя и так далее. Там я также сохраняю указатель на следующий RequestIo
объект, чтобы иметь навязчивый связанный список этих объектов.
Затем Stream
объект имеет 2 заголовка этого навязчивого связанного списка, один для RequestIo
объектов для чтения, а другой для записи. Таким образом, Stream
объект может иметь небольшой пул этих RequestIo
объектов, и ему не нужно выделять / освобождать их при каждой операции ввода-вывода и не нуждается в блокировке, поскольку 2 навязчивых списка разделены для чтения и записи, которые представляют собой 2 вида операций, которые могут выполняться в 2 разных потоках одновременно вIOCPs.
Когда у меня будут потоковые объекты (такие как сокеты или каналы) У меня будет только 1 RequestIo
для чтения (больше одного просто не нужно) и один для записи, поэтому мне в принципе не нужна блокировка, потому что сначала socket.read()
RequestIo
выделяется новый, вставляется в связанный список, и он будет использоваться снова и снова, пока сокет не будет закрыт и уничтоженто же самое для записей.
Но нет потокоподобных объектов (таких как файл произвольного доступа, сокеты udp), которые могут выдавать более одного RequestIo
как для чтения, так и для записи. Давайте просто рассмотрим сокет UDP, который может выдавать N ожидающих RequestIo
объектов для чтения дейтаграмм, или файл произвольного доступа, который может выдавать несколько RequestIo
пакетов для чтения / записи в / из разных частей файла.
Здесь все усложняется. Если у меня есть этот связанный список RequestIo
объектов, мне действительно нужно просмотреть этот список и посмотреть, какой RequestIo
из них НЕ находится на рассмотрении, и выполнить с ним новую операцию ввода-вывода.
Сказал, что это кажется простым, но это не так: несмотря на то, что я могу установить флаг на a RequestIo
с надписью «его ожидает», проблема не решена: разве этот флаг не должен быть атомарным целым числом? Поскольку этот флаг будет сброшен каким-либо другим потоком. А как насчет извлечения первого RequestIo
доступного из связанного списка, когда существует несколько RequestIo
экземпляров? Разве это не должно быть взаимосвязанной операцией? И вставка в этот связанный список? Например. когда я выделяю новый RequestIo
пакет, потому что все остальные находятся на рассмотрении.
Возможное решение, о котором я думал, — это пройти по этому связанному списку и проверить атомарное целое число в RequestIo
объекте с помощью инструкции CAS (CompareAndSwap), если 0, это означает, что оно не находится на рассмотрении, и немедленно установить его равным 1, чтобы другой поток увидел его как ожидающий и перешел к следующему RequestIo
объекту. Если он не может найти какой-либо RequestIo
объект, он выделяет новый, но здесь он должен заблокировать связанный список head…to вставьте новый выделенный RequestIo
объект!
Итак, каков, по сути, самый быстрый и эффективный способ корректного управления пулом из N OVERLAPPED
(или RequestIo
в моем случае) объектов, не подвергаясь массивной блокировке, которая снизила бы производительность и назначение многопоточных IOCP?
Комментарии:
1. Я не понимаю, почему у вас вообще есть отложенные экземпляры RequestIo в списке? Вы передали их подсистеме IOCP с помощью вызовов WSABlah. Вы получите их обратно, когда операция ввода-вывода будет завершена (или не удалась:), так почему вы также сохраняете их в списке?
2. Кроме того, даже для потоковых сервисов я обычно стараюсь держать в ожидании как минимум два запроса на получение, чтобы система IOCP часто не попадала в ситуацию, когда для входящих данных недоступны пользовательские буферы. Что касается записи, часто встречаются множественные запросы ввода-вывода, поскольку, если буфер доступен для записи, вы можете отправить его сейчас, а не ждать, пока полный буферный массив, представляющий, скажем, HTTP-пакет HTML-файла / страницы, будет собран полностью перед отправкой первогофрагмент.
3. У меня они есть в связанном списке, поэтому мне не нужно каждый раз уничтожать и перераспределять их, и я могу использовать их повторно, даже когда я закрываю сокет и снова открываю его. Я выделяю их один раз и повторно использую. В основном объект
Stream
имеет_a NRequestIo
объектов в небольшом пуле связанных списков. Таким образом, когда вам нужно выполнить какой-либо ввод-вывод, вы беретеRequestIo
из этого связанного списка и используете его для своей операции ввода-вывода. В противном случае, что вы делаете после завершения операции вводаRequestIo
-вывода? В случае чтения вы можете выдать новое чтение с тем же объектом, но в случае записи?4. @MarcoPagliaricci вы слишком беспокоитесь о блокировках. Критический раздел должен работать нормально, поскольку он имеет первичную блокировку вращения, а окно конкуренции очень маленькое — вы только нажимаете / нажимаете указатель.
5. Кроме того, опять же, я не могу понять, почему вы настаиваете на отслеживании тех экземпляров, которые используются. Как предлагает Гарри ниже, просто поместите использованные экземпляры обратно в свободный пул в потоке обработчика завершения, когда вы закончите с любыми буферами данных, содержащимися внутри. То же самое с сокетами, то же самое с экземплярами буфера данных.
Ответ №1:
Сохраняйте связанный список, содержащий только неиспользуемые RequestIo
объекты. Вы можете вставлять объект из заголовка списка всякий раз, когда он вам нужен, и возвращать каждый объект обратно в список, когда закончите с ним.
Функции InitializeSListHead, InterlockedPushEntrySList и InterlockedPopEntrySList обеспечивают эффективную многопроцессорную реализацию связанного списка.
Комментарии:
1. Я уже думал об этом, но и здесь мне понадобится механизм блокировки для этих списков. Я имею в виду:
lock(); get_first_available_RequestIo_for_writing(); remove_it_from_available_list(); unlock();
и так далее2. Да, вам нужна блокировка, но кого это волнует? Это будет критический раздел, и вероятность фактического конфликта (и, следовательно, требующий блокировки ядра вместо блокировки CS) минимальна. Все, что вы делаете, это нажимаете / нажимаете один указатель на экземпляр RequstIo — это не займет много времени!
3. Если пул объектов requestIo заканчивается, вам нужна стратегия обработки. Либо создайте еще один запрос, тем самым увеличив размер пула, либо организуйте пул как очередь блокировки, чтобы запрашивающий поток должен был ждать освобождения экземпляров.
4. Вам не нужна блокировка. Взаимосвязанные функции push / pop являются атомарными и многопроцессорными, т. Е. Они обрабатывают блокировку за вас.
5. Гарри: о, да, с теми API, которые вы предоставили, в основном я буду использовать связанный список без блокировок. Спасибо.