Многопоточность сети - PullRequest
       31

Многопоточность сети

4 голосов
/ 27 сентября 2008

Я программирую онлайн-игру по двум причинам, одна из которых состоит в том, чтобы ознакомиться с запросами сервера / клиента в среде реального времени (в отличие от чего-то вроде обычного веб-браузера, который не работает в режиме реального времени) и на самом деле намочить руки в этой области, так что я могу приступить к собственно правильно спроектировать один.

В любом случае, я делаю это на C ++ и использую winsock для своих базовых, базовых сетевых тестов. Я, очевидно, хочу использовать ограничитель кадров и запускать 3D и все это в какой-то момент, и моя главная проблема заключается в том, что когда я выполняю send () или receive (), программа любезно работает там и ждет ответа. Это может привести к 8 к / с даже при лучшем интернет-соединении.

Итак, очевидное решение для меня - вынуть сетевой код из основного процесса и запустить его в своем собственном потоке. В идеале, я бы назвал «send» в моем основном процессе, который передавал бы сетевому потоку указатель на сообщение, а затем периодически (каждый кадр) проверял бы, получил ли сетевой поток ответ, или тайм-аут, или что есть ты В идеальном мире у меня фактически было бы 2 или более сетевых потоков, работающих одновременно, так что я мог бы сказать, запустить окно чата и выполнить фоновую загрузку фрагмента брони, и при этом позволить игроку бегать вокруг все сразу.

Основная часть моей проблемы в том, что это для меня новость. Я понимаю концепцию потоков, но я вижу некоторые серьезные проблемы, например, что происходит, если два потока пытаются читать / записывать один и тот же адрес памяти одновременно, и т. Д. Я знаю, что уже есть методы на месте, чтобы справиться с подобными вещами, поэтому я ищу предложения о наилучшем способе реализации чего-то подобного. По сути, мне нужен поток A, чтобы иметь возможность запускать процесс в потоке B, отправляя порцию данных, опрашивая состояние потока B, а затем получая ответ, также в виде порции данных. В идеале, без каких-либо серьезных сбоев. ^ _ ^ Я буду беспокоиться о том, что на самом деле содержат эти данные и как обрабатывать пропущенные пакеты и т. Д. Позже, мне просто нужно, чтобы это сначала произошло.

Спасибо за любую помощь / совет.

PS: Просто подумав об этом, может упростить вопрос. Есть ли способ использовать систему обработки событий Windows в моих интересах? Например, возможно ли, чтобы поток A инициализировал данные где-нибудь, а затем вызвал событие в потоке B, чтобы он забрал данные, и наоборот, чтобы поток B сообщил потоку A, что это было сделано? Это, вероятно, решило бы большинство моих проблем, поскольку мне не нужны оба потока для одновременной работы с данными, на самом деле это больше, чем просто эстафета. Я просто не знаю, возможно ли это между двумя разными потоками. (Я знаю, что один поток может создавать свои собственные сообщения для обработчика событий.)

Ответы [ 6 ]

7 голосов
/ 27 сентября 2008

Самая легкая вещь

для вас будет просто вызвать Windows API QueueUserWorkItem . Все, что вам нужно указать, это функцию, которую будет выполнять поток, и входные данные, переданные ему. Пул потоков будет создан автоматически для вас и заданий, выполняемых в нем. Новые темы будут создаваться по мере необходимости.

http://msdn.microsoft.com/en-us/library/ms684957(VS.85).aspx

Больше контроля

Вы могли бы иметь более подробный контроль, используя другой набор API, который может снова управлять пулом потоков для вас -

http://msdn.microsoft.com/en-us/library/ms686980(VS.85).aspx

Сделай сам

Если вы хотите контролировать все аспекты создания потоков и управления пулом, вам придется создавать потоки самостоятельно, решать, как они должны заканчиваться, сколько создавать и т. Д. . Если вы используете MFC, вы должны использовать функцию AfxBeginThread).

Отправка заданий в рабочие потоки - порты завершения ввода-вывода

В этом случае вам также придется беспокоиться о том, как сообщить о своей работе - я бы порекомендовал IoCOmpletionPorts для этого. Это самый масштабируемый механизм уведомлений, который я в настоящее время знаю, созданный для этой цели. У него есть дополнительное преимущество, заключающееся в том, что он реализован в ядре, поэтому вы избегаете всевозможных ситуаций, связанных с мертвой блокировкой, с которыми вы столкнетесь, если решите сами что-то обработать.

Эта статья покажет вам, как с примерами кода -

http://blogs.msdn.com/larryosterman/archive/2004/03/29/101329.aspx

Обратная связь - Сообщения Windows

Вы можете использовать сообщения Windows, чтобы сообщить о статусе вашему родительскому потоку, так как он все равно выполняет ожидание сообщения. используйте функцию PostMessage, чтобы сделать это. (и проверьте на ошибки)

ps: Вы также можете разместить данные, которые нужно отправить, по выделенному указателю, и тогда рабочий поток может позаботиться об их удалении после отправки. Таким образом, вы также избегаете трафика обратного указателя.

3 голосов
/ 27 сентября 2008

Предложение BlodBath о неблокирующих сокетах является потенциально правильным подходом.

Если вы пытаетесь избежать использования многопоточного подхода, тогда вы можете исследовать использование настройки перекрывающихся операций ввода-вывода на ваших сокетах. Они не будут блокироваться, когда вы выполняете передачу или прием, но у них есть дополнительный бонус, который дает вам возможность ожидания нескольких событий в пределах одного цикла событий. Когда передача завершена, вы получите событие. (см. это для некоторых деталей)

Это не является несовместимым с многопоточным подходом, поэтому есть возможность изменить свое решение позже. ; -)

О дизайне вашего многопоточного приложения. лучшее, что нужно сделать, - это проработать все внешние действия, о которых вы хотите получать оповещения. Например, на данный момент в вашем вопросе вы перечислили передачу по сети, получение по сети и активность пользователя. В зависимости от количества одновременных соединений, с которыми вы собираетесь иметь дело, вам, вероятно, будет концептуально проще иметь поток на сокет (при условии небольшого количества сокетов), где каждый поток отвечает за всю обработку этого сокета. .

Затем вы можете внедрить какую-либо форму системы обмена сообщениями между вашими потоками, как предложено RC. Организуйте свою систему так, чтобы, когда сообщение отправлялось в определенный поток, и событие также отправлялось. Затем ваши темы могут быть отправлены в спящий режим в ожидании одного из этих событий. (а также любые другие стимулы, такие как события сокетов, пользовательские события и т. д.)

Вы совершенно правы, что вам нужно быть осторожным в ситуациях, когда более одного потока пытаются получить доступ к одному и тому же фрагменту памяти. Здесь можно использовать мьютексы и семафоры.

Также помните об ограничениях, которые имеет ваш графический интерфейс, когда дело доходит до многопоточности. Некоторое обсуждение по этому вопросу можно найти в этом вопросе . Но сокращенная версия состоит в том, что большинство (и Windows является одним из них) графических интерфейсов не позволяют нескольким потокам одновременно выполнять операции графического интерфейса. Чтобы обойти эту проблему, вы можете использовать насос сообщений в своем приложении, отправив пользовательские сообщения в ваш поток графического интерфейса, чтобы получить it для выполнения операций графического интерфейса.

2 голосов
/ 27 сентября 2008

Я предлагаю заглянуть в неблокирующие розетки для быстрого исправления. Использование неблокирующих сокетов send () и recv () не блокирует, а используя функцию select (), вы можете получать любые ожидающие данные каждый кадр.

0 голосов
/ 27 сентября 2008

Некоторые темы, которые могут вас заинтересовать:

  • мьютекс : мьютекс позволяет блокировать доступ к определенным ресурсам только для одного потока

  • семафор : способ определить, сколько пользователей еще имеет определенный ресурс (= сколько потоков к нему обращается) и способ доступа потоков к ресурсу. Мьютекс - это особый случай семафора.

  • критическая секция : часть кода, защищенная мьютексом (улица с одной полосой движения), по которой может перемещаться только один поток за раз.

  • очередь сообщений : способ распределения сообщений в централизованной очереди

  • межпроцессное взаимодействие (IPC) - способ взаимодействия потоков и процессов друг с другом по именованным каналам, совместно используемой памяти и многими другими способами (это скорее концепция, чем специальный техника)

Все темы, выделенные жирным шрифтом, можно легко найти в поисковой системе.

0 голосов
/ 27 сентября 2008

Я не кодирую игры, но я использовал систему, похожую на ту, что предложила pukku. Он прекрасно подходит для таких вещей, как наличие буфера в качестве приоритета для обработки ваших сообщений, если у вас есть такая необходимость.

Я думаю о них как о почтовых ящиках в потоке. Вы хотите отправить пакет? Пусть ProcessThread создаст «потоковое сообщение» с полезной нагрузкой для подключения к сети и «отправит» его в NetworkThread (т. Е. Поместит его в очередь / почтовый ящик NetworkThread и сообщит переменную условия NetworkThread, чтобы он проснулся и вытащить это). Когда NetworkThread получает ответ, упакуйте его в сообщение потока и отправьте его обратно в ProcessThread таким же образом. Разница в том, что ProcessThread не будет заблокирован в условной переменной, просто запросив mailbox.empty (), если вы хотите проверить ответ.

Возможно, вы захотите нажать и вытолкнуть напрямую, но более удобный способ для больших проектов - реализовать схему toThreadName, fromThreadName в базовом классе ThreadMsg и почтовое отделение, в котором потоки регистрируют свой почтовый ящик. PostOffice затем отправляет (ThreadMsg *); функция, которая получает / отправляет сообщения в соответствующий почтовый ящик на основе от и до. Почтовый ящик (класс buffer / queue) содержит ThreadMsg * = receiveMessage (), в основном выталкивая его из базовой очереди.

В зависимости от ваших потребностей, ThreadMsg может содержать процесс виртуальной функции (..), который может быть соответствующим образом переопределен в производных классах, или просто иметь обычный класс ThreadMessage с членами to, from и функцией getPayload () для верните необработанные данные и обработайте их непосредственно в ProcessThread.

Надеюсь, это поможет.

0 голосов
/ 27 сентября 2008

Рассматривайте это как проблема производителя-потребителя : при получении ваш сетевой коммуникационный поток является производителем, а поток пользовательского интерфейса - потребителем. При отправке все наоборот. Реализуйте простой буферный класс, который дает вам такие методы, как push и pop (pop должен быть блокирующим для сетевого потока и неблокирующим для потока пользовательского интерфейса).

Вместо того, чтобы использовать систему событий Windows, я бы предпочел что-то более переносимое, например Переменные условия Boost .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...