Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
Разработка программного обеспечения

Thread coordination using Boost.Atomic

Boost , The Boost C++ Libraries BoostBook Documentation Subset , Chapter 6. Boost.Atomic

Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

PrevUpHomeNext

Наиболее распространенное использованиеBoost.Atomicдля реализации пользовательских протоколов синхронизации потоков: Цель состоит в координации доступа потоков к общим переменным, чтобы избежать «конфликтов». Программист должен знать, что компиляторы, процессоры и иерархии кэша могут, как правило, переупорядочивать ссылки на память по желанию. Как следствие, такая программа, как:

int x = 0, int y = 0;
thread1:
  x = 1;
  y = 1;
thread2
  if (y == 1) {
    assert(x == 1);
  }

Это может привести к провалу, поскольку нет никакой гарантии, что чтение<x>по потоку 2 «видит» запись по потоку 1.

Boost.Atomicиспользует концепцию синхронизации, основанную на отношении, происходящем до, для описания гарантий, при которых такие ситуации, как вышеупомянутая, не могут возникнуть.

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

В качестве вводного примера, чтобы понять, как аргументация с использованиемпроисходит до того, какработает, рассмотрим две нити, синхронизирующиеся с использованием общего мутекса:

mutex m;
thread1:
  m.lock();
  ... /* A */
  m.unlock();
thread2:
  m.lock();
  ... /* B */
  m.unlock();

«Интуиция, основанная на блокировке», будет утверждать, что A и B не могут выполняться одновременно, поскольку кодовые пути требуют общего замка.

Однако можно прийти к такому же выводу, используя, прежде чем: Либо поток1, либо поток2 преуспеют первыми<m.lock()>. Если это поток1, то, как следствие, поток2 не может преуспеть в<m.lock()>до того, как поток1 выполнил<m.unlock()>, следовательно, A.— доВ в этом случае. По симметрии, если нить 2 преуспевает на<m.lock()>первой, мы можем заключить B.происходит доА.

Поскольку это уже исчерпывает все варианты, мы можем заключить, что либо Апроисходит — доВ, либо Впроисходит — доА должно всегда держаться. Очевидно, что они не могут указать, чтоиз двух отношений, но одного достаточно, чтобы заключить, что A и B не могут конфликтовать.

Сравните реализациюspinlock, чтобы увидеть, как концепция взаимного исключения может быть отображена на.Boost.Atomic.

Самый простой шаблон для координации потоковBoost.Atomicиспользует<release>и<acquire>на атомной переменной для координации: Если...

  • ... нить 1 выполняет операцию А,
  • ... нить 1 впоследствии записывает (или атомарно модифицирует) атомную переменную с<release>семантической,
  • ... thread2 считывает (или атомарно считывает и модифицирует) значение этого значения из той же атомной переменной с<acquire>семантической и
  • ...поток 2 впоследствии выполняет операцию В,

Апроисходит доВ.

Рассмотрим следующий пример

atomic<int> a(0);
thread1:
  ... /* A */
  a.fetch_add(1, memory_order_release);
thread2:
  int tmp = a.load(memory_order_acquire);
  if (tmp == 1) {
    ... /* B */
  } else {
    ... /* C */
  }

В этом примере возможны два пути исполнения:

  • <store>работа по потоку1 предшествует<load>по потоку2: В этом случае поток 2 будет выполнять B, а «Aпроисходит доB» выполняется, поскольку все вышеперечисленные критерии удовлетворены.
  • <load>работа по потоку2 предшествует<store>по потоку1: В этом случае поток 2 будет исполнять C, но «Aпроисходит — доC» не удерживает: поток 2 не считывает значение, записанное потоком 1 через<a>.

Следовательно, A и B не могут конфликтовать, а A и Cмогутконфликтовать.

Ограничения упорядочивания обычно указываются вместе с доступом к атомной переменной. Однако также возможно проводить операции «забора» изолированно, в этом случае забор работает в сочетании с предыдущими (для<acquire>,<consume>или<seq_cst>операциями) или последующими (для<release>или<seq_cst>) атомными операциями.

Пример из предыдущего раздела также может быть написан следующим образом:

atomic<int> a(0);
thread1:
  ... /* A */
  atomic_thread_fence(memory_order_release);
  a.fetch_add(1, memory_order_relaxed);
thread2:
  int tmp = a.load(memory_order_relaxed);
  if (tmp == 1) {
    atomic_thread_fence(memory_order_acquire);
    ... /* B */
  } else {
    ... /* C */
  }

Это обеспечивает те же гарантии заказа, что и ранее, но исключает (возможно, дорогостоящую) операцию заказа памяти в случае C.

Второй шаблон для координации потоков черезBoost.Atomicиспользует<release>и<consume>на атомной переменной для координации: Если...

  • ... нить 1 выполняет операцию А,
  • ... нить 1 впоследствии записывает (или атомарно модифицирует) атомную переменную с<release>семантической,
  • ... thread2 считывает (или атомарно считывает и модифицирует) значение этого значения из той же атомной переменной с<consume>семантической и
  • ...поток 2 впоследствии выполняет операцию В, котораявычислительно зависит от значения атомной переменной,

Апроисходит доВ.

Рассмотрим следующий пример

atomic<int> a(0);
complex_data_structure data[2];
thread1:
  data[1] = ...; /* A */
  a.store(1, memory_order_release);
thread2:
  int index = a.load(memory_order_consume);
  complex_data_structure tmp = data[index]; /* B */

В этом примере возможны два пути исполнения:

  • <store>работа по потоку1 предшествует<load>по потоку2: В этом случае поток 2 будет читать<data[1]>и «Апроисходит доВ», поскольку все вышеуказанные критерии удовлетворены.
  • Работа<load>по потоку2 предшествует<store>по потоку1: В этом случае поток 2 будет читать<data[0]>, а «Aпроисходит доB» не удерживает: поток 2 не считывает значение, записанное потоком 1 - 121 .

Здесьпроисходит - доотношения помогают гарантировать, что любые доступы (предположительно записывается) к<data[1]>по потоку1 происходят до того, как доступы (предположительно читается) к<data[1]>по потоку2: Не имея этой связи, поток 2 может видеть устаревшие / непоследовательные данные.

Обратите внимание, что в этом примере тот факт, что операция B вычислительно зависит от атомной переменной, поэтому ошибочна будет следующая программа:

atomic<int> a(0);
complex_data_structure data[2];
thread1:
  data[1] = ...; /* A */
  a.store(1, memory_order_release);
thread2:
  int index = a.load(memory_order_consume);
  complex_data_structure tmp;
  if (index == 0)
    tmp = data[0];
  else
    tmp = data[1];

<consume>Наиболее часто (и наиболее безопасно) См.ограничения, используемые с указателями, сравните, например,синглтон с двойной проверкой блокировки.

Третий шаблон для координации потоков черезBoost.Atomicиспользует<seq_cst>для координации: Если...

  • ... нить 1 выполняет операцию А,
  • ... нить 1 впоследствии выполняет любую операцию с<seq_cst>,
  • ... нить 1 впоследствии выполняет операцию В,
  • ... резьба 2 выполняет операцию C,
  • ... нить 1 впоследствии выполняет любую операцию с<seq_cst>,
  • ...поток 2 впоследствии выполняет операцию D,

Апроисходит доD или Спроисходит доB.

При этом не имеет значения, работают ли нити 1 и нити 2 на одних и тех же или разных атомных переменных, или используют операцию «в одиночку»<atomic_thread_fence>.


PrevUpHomeNext

Статья Thread coordination using Boost.Atomic раздела The Boost C++ Libraries BoostBook Documentation Subset Chapter 6. Boost.Atomic может быть полезна для разработчиков на c++ и boost.




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



:: Главная :: Chapter 6. Boost.Atomic ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-08-30 11:47:00
2025-05-19 17:27:51/0.030198097229004/1