Приведет ли это к состоянию гонки в программировании на основе событий?
Я пишу небольшую агентную симуляцию взаимодействия в дискретном симуляторе и начинаю писать код, который выглядит следующим образом. Раньше у меня не было событийно-ориентированного программирования, но я не наблюдал этой ситуации. Мне интересно, приведет ли следующий фрагмент кода к состоянию гонки при обновлении значения msgRcvd
,
// Following is the event-loop per-se
Controller {
if (...) {
SendMessage(currentTime() + 5, i,j)
SendMessage(currentTime() + 5, i,k)
}
print currentTime(), msgsRcvd
Schedule(currentTime()+1, Controller)
}
// The following function is called when an
// agent receives a message
Receive(Agent agent) {
if (...) {
msgsRcvd++ // <-- this is a global variable
}
}
Я понимаю, что в currentTime() + 5
оба агента получают сообщение одновременно, потому что оба события происходят в одно и то же логическое время, поэтому я должен увидеть количество сообщений, равное 2? Или я увижу какое-то странное состояние гонки, и это значение зависит от планировщика (т. Е. Может закончиться выводом 1 или 2)? Какие-либо предложения?
3 ответа
Ответ зависит от реализации вашего транспорта событий и не зависит от языка в этом смысле.
Во всех системах, с которыми я работал, каждое сообщение было бы помещено отдельно в очередь событий, и агент-получатель мог бы вынимать события из этой очереди по порядку. Предполагая, что у вас есть один поток, генерирующий сообщения, и одно событие, удаляющее сообщения из очереди, я не вижу возможности для состояния гонки.
Если в вашей очереди событий есть какой-то интеллект, который пытается консолидировать события на основе отметки времени, вы будете видеть только одно событие в получающем агенте. Мне неизвестна обычная система, которая это делает (хотя некоторые системы пользовательского интерфейса могут, например, объединить два быстрых щелчка мышью в двойной щелчок... но это специфическое поведение конкретной системы событий, а не независимость от языка / платформы).
Первоначально собирался упомянуть, что нет никакого независимого от языка / платформы способа ответить на этот вопрос, но Эрик Дж. Об этом рассказал.
В C++ этот код в том виде, в котором он написан, не будет безопасным, если ваша платформа не гарантирует сериализацию обратных вызовов. Причина в том, что оператор приращения не является атомарным, и если два потока одновременно пытаются обновить значение, может произойти любое количество вещей в зависимости от порядка выборки, добавления и сохранения.
Если эта платформа действительно спроектирована с учетом параллелизма, то должен существовать "взаимосвязанный / атомарный" API, обеспечивающий необходимую вам функцию.
Нет, несмотря на то, что код агента очень подозрительный и выглядит опасным, я не вижу, чтобы в этом случае он вызывал состояние гонки: msgsRcvd всегда должен приводить к правильному итогу. Даже если планировщик прерывает agent1 непосредственно перед приращением, мне кажется, что управление всегда вернется, чтобы завершить это приращение. Если контроллер получает управление, то может случиться так, что он сообщит о неточном содержимом MsgsRcvd, но что с того? MsgsRcvd быстро возвращается в фазу.
Это, конечно, страшно выглядящий кусок кода. Когда я смотрю на этот вид кода, я всегда хочу переместить инкремент MsgsRcvd вверх в контроллер, открывая там функцию, которая будет делать инкремент. Но это только заставило бы меня чувствовать себя лучше в этом случае; это не изменило бы логику и не решило бы "проблему" (если она есть) временного неточности MsgsRcvd.