Почему бы не использовать sync.map без разбора в одновременных сценариях?

задняя часть Go

«Эта статья участвовала в мероприятии Haowen Convocation Order, щелкните, чтобы просмотреть:Двойные заявки на внутреннюю и внешнюю стороны, призовой фонд в 20 000 юаней ждет вас, чтобы бросить вызов!"

сама карта не является безопасной для параллелизма

Все мы знаем, что карта go небезопасна для параллелизма, когда несколько горуотинов читают и пишут карту одновременно, возникают проблемы с параллельной записью.fatal error: concurrent map writes

carbon-7.png

  1. В начале программы мы инициализируем карту
  2. Дочерняя горутина присваивает значение m[a]
  3. Основная горутина присваивает значение m[a]

Теоретически, пока дочерняя горутина и основная горутина работают одновременно под многоядерным процессором, будут проблемы. Мы могли бы также использовать тот, который идет с go-race чтобы проверить, вы можете запустить go run -race main.go

carbon-8.pngПутем обнаружения мы можем обнаружить, что существуетdata race, то есть проблема гонки данных. Некоторые люди говорят, что это просто и может быть решено с помощью блокировки.Конечно, блокировка может быть решена, но вы знаете, накладные расходы на блокировку.
Оставив в стороне проблему гонок данных, мы можем понять накладные расходы на блокировку, взглянув на пример:

carbon-9.png

  1. BenchmarkAddMapWithUnLockтест без блокировки
  2. BenchmarkAddMapWithLockтестирование заблокировано

пройти черезgo test -bench .Запустим тест и получим следующие результаты:

carbon (22).pngМожно обнаружить, что среднее время, проведенное без замков, составляет около6.6 ms, среднее время, проведенное с замком, составляет около7.0 ms, хотя разница почти такая же, но она также отражает накладные расходы на блокировку. В некоторых сложных случаях это может быть более очевидным.

sync.map

Некоторые люди говорят, что раз блокировка стоит дорого, то используйте встроенный метод sync.map из go, который может решить проблему параллелизма. sync.map действительно может решить проблему concurrent map, но он больше подходит в случае больше читать и меньше писать, он может обеспечить безопасность параллелизма, и при этом не требует накладных расходов на блокировки, может быть и хуже в случае большего количества написания и меньшего чтения, в основном из-за его дизайна, давайте посмотрим на анализ исходного кода:

структура

carbon (23).png

  1. блокировка мьютекса, когда дело доходит до грязных данных (грязных) операций, вам нужно использовать эту блокировку
  2. чтение, чтение не нужно блокировать, оно читается из чтения, чтение имеет тип atomic.Value, конкретная структура выглядит следующим образом:

carbon (25).pngДанные чтения существуют в readOnly.m, который также является картой, значение — это указатель на запись, а запись — это структура.Конкретные типы следующие:

carbon (26).pngВ нем есть p.Когда мы устанавливаем значение ключа, можно понять, что p — это указатель на значение (p — это адрес значения).
когдаreadOnly.amended = trueКогда прочитанные данные не самые последние, грязные данные содержат некоторые новые ключи, которых нет у прочитанных.
3.Грязная карта это тоже тип карты.С точки зрения именования она грязная.Можно понять,что при добавлении нового кв в некоторых сценариях,он сначала будет добавлен в грязную,которая новее,чем прочитанная .
4. Карта промахов, когда данные не считываются из чтения и изменено = true, она будет пытаться читать из грязного, а промахи будут увеличиваться на 1, когда количество промахов больше или равно длине грязного, это Грязные будут назначены для чтения, а пропущенные и грязные сброшены.

Например

Основная идея sync.map — обменять пространство на время.
Предположим теперь, что выставлена ​​художественная выставка (read) n картин приходит посмотреть группа людей, каждый может увидеть то, что хочет увидеть на этой выставке, без ожидания и очереди. В это время была поставлена ​​новая картина, но поскольку картина демонстрировалась в рабочее время, ее нельзя было повесить напрямую, и, возможно, новую картину необходимо было поддерживать, поэтому она не будет отображаться на выставке для время (read), поэтому он сначала помещается в резервное хранилище (dirty), если кто-то очень хочет увидеть новую картину, то могут только отвезти его на склад (dirty), допустим в это время приходит новая картина, а на складе n+1 картин.В это время кто-то спрашивает есть ли эта новая картина.Менеджер сказал: Да мы с вами пойдем на склад Проверить это из. В это время кто-то пришел спросить: «Есть ли новая картина?» Менеджер сказал: «Да, мы с вами пойдем на склад посмотреть. На вопрос, достигло ли количество показов этой новой картины n+1, владелец художественной выставки обнаружил, что по-прежнему много людей смотрят эту новую картину. Так я и говорю менеджеру: идите смотрите, ждите пока никто не увидит выставку(read), поставить выставку(read) все картины сброшены, а все картины на складе (грязные) заменены. При смене всех менеджеров выставка (read) уже самая полная и актуальная картина.
Принцип sync.map, вероятно, аналогичен приведенному выше примеру.新的k、v) если интересно, отвезите его на склад (dirty) Смотри, потому что в это время только один менеджер, ты можешь привести только одного человека за раз (加锁), низкая эффективность, другие картины, на выставке (read), просто посмотрите на это, и эффективность высока.

Магазин (добавить или обновить кв)

carbon (27).png

  1. Когда ключ существует в режиме чтения, пришло время обновить значение, попытаться обновить значение напрямую и вернуться, если обновление прошло успешно, блокировка не требуется. В нем есть tryStore:

carbon (29).pngЕсть мнение в tryStorep == expungedвозвращает ложь. Существует три типа п:nil(Когда ключ в режиме чтения удаляется, он фактически удаляется мягко, просто установите p равным нулю),expunged(Удаленный ключ (p==nil) будет удален, если прочитанная копия станет грязной),其他正常的value的地址, здесь, если оно удалено, не выбирайте обновление значения.

  1. Блокировка, следующая вещь — потокобезопасность.
  2. В процессе блокировки может оказаться ключ, которого не было в исходном замке, поэтому нужно еще раз его проверить.Если он существует в считывании и изначально был удален грязным, то восстановить ключ в грязном, и, наконец, установите значение.
  3. Если в read нет ключа, а в dirty есть, то напрямую изменить значение
  4. Если такого ключа нет в read и dirty, а dirty равно nil, попробуйте скопировать неудаленные в read в dirty (удаление в read на самом деле не удаляется, оно установит entry.p в nil, простое понимание. установить адрес значения ключа равным nil), что все делается в dirtyLocked:

carbon (30).pngЗатем установите новые k и v в dirty. (Здесь можно обнаружить, что новые k и v добавляются в грязную карту первыми, а чтение недоступно).
6. Теперь dirty — это относительно чистые данные (ключи nil или expunged были очищены), установите исправленный = true (указывая, что dirty в это время не пусто, и в dirty есть новые данные)
7. Разблокировать
Суммировать:

  1. Можно обнаружить, что для обновления, чтения и загрязнения обновляются, потому что значение является указателем, а нижний слой является значением.
  2. Для новых они сначала будут добавлены в dirty, а не в read.
  3. Для новых дополнений требуются замки, так что допустим крайний случай: продолжайте добавлять новые ключи, тогда каждый раз требуются замки, не говоря уже о том, что есть ещеif elseотраслевое решение. Общая производительность определенно хуже, чем у обычной блокировки карты.

Нагрузка (получить кВ)

carbon (28).png

  1. Когда ключ не существует в режиме чтения и amenbed=true (через указанное выше хранилище, указывающее, что в данный момент у dirty есть новые данные), блокировка (dirty не является потокобезопасной)
  2. Из-за процесса блокировки чтение может измениться, поэтому проверьте еще раз
  3. Перейти к грязным, чтобы получить данные
  4. Через мисслок, есть он или нет, сначала +1 к промахам, если количество промахов >=len(dirty), то копировать грязный на чтение, чтобы считанные данные были самыми последними
  5. Переработаны грязные и промахи.

carbon (31).png6. Если соответствующего ключа нет, вернуть nil, если есть, вернуть соответствующее значение

Суммировать:

  1. Если в чтении есть ключ, нет необходимости блокировать его и возвращать напрямую, что эффективно и удобно для сценариев с большим количеством чтений.
  2. Если у dirty есть ключ, отмените чтение, записав количество промахов, выдержите время блокировки, вызванное промахом, и, наконец, прочитайте чтение для нового ключа.

Удалить (удалить k)

carbon (32).png

  1. Когда ключ не существует в режиме чтения и у грязных есть новые данные, блокировка
  2. Из-за процесса блокировки чтение может измениться, поэтому проверьте еще раз
  3. Когда есть новые данные в грязном, удалите k в грязном напрямую
  4. Если есть чтение, затем мягкое удаление, установите p на ноль

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

вернуться к теме

Проанализировав sync.map, мы обнаружили, что в случае большего чтения и меньшего письма он все еще относительно хорош, по сравнению с обычной блокировкой карты, безусловно, лучше, но в случае большего количества записи и меньшего чтения не подходит, т.к. все равно предполагает частые блокировки, чтение и грязный обмен и прочие накладные расходы.Может даже хуже, чем производительность штатной блокировки карты. Давайте рассмотрим это на крайнем примере:

carbon (34).png

  1. BenchmarkAddMapWithUnLockтест без блокировки
  2. BenchmarkAddMapWithLockтестирование заблокировано
  3. BenchmarkAddMapWithSyncMapэто тест sync.map

Эти три метода заключаются в том, чтобы добавить на карту 10 Вт фрагментов данных.

пройти черезgo test -bench .Запустим тест и получим следующие результаты:

carbon (35).pngВидно, что время, затрачиваемое на sync.map, примерно в 5 раз больше, чем на два других. sync.map — вещь хорошая, но неправильное использование сцены контрпродуктивно.

image.png