Взаимодействие между атомами C и C ++ - PullRequest
0 голосов
/ 22 декабря 2018

Предположим, у меня есть задача, которая может быть отменена из другого потока.Задача выполняется в функции C, другой поток выполняет код C ++.Как мне это сделать?

Пример грубый.

C:

void do_task(atomic_bool const *cancelled);

C ++:

std::atomic_bool cancelled;
…
do_task(&cancelled);

На данный момент я создал файлatomics.h со следующим содержанием:

#ifdef __cplusplus
#include <atomic>
using std::atomic_bool;
#else
#include <stdatomic.h>
#endif

Кажется, это работает, но я не вижу никаких гарантий для этого.Интересно, есть ли лучший (правильный) способ.

Ответы [ 5 ]

0 голосов
/ 23 декабря 2018

насколько я понимаю, ваш код в общем (должен быть) следующий

// c code

void _do_task();

void do_task(volatile bool *cancelled)
{
  do {
    _do_task();
  } while (!*cancelled);
}

// c++ code

volatile bool g_cancelled;// can be modify by another thread
do_task(&cancelled);

void some_proc()
{
  //...
  g_cancelled = true;
}

я задам вопрос - здесь нам нужно объявить cancelled как атомарный?нам нужно атомное здесь?

атомная потребность в 3 случаях:

  • мы делаем R ead- M odify- Ш обряд.скажем, если нам нужно установить cancelled в true и проверить, было ли это уже true.это, например, может понадобиться, если для нескольких потоков задано значение cancelled, равное true, и для тех, кто делает это первым, необходимо освободить некоторые ресурсы.

    if (!cancelled.exchange(true)) { free_resources(); }

  • для чтения или записиоперация для типа должна быть атомарной.конечно, для всех текущих и всех возможных будущих реализаций это верно для типа bool (несмотря на то, что формальное не определено).но даже это не важно.мы здесь проверяем cancelled только на 2 значения - 0 (false) и все другое.таким образом, даже если операции записи и чтения в отмененном режиме предполагают не атомарную, после того, как один поток записывает ненулевое значение в отмененное состояние, другой поток рано или поздно прочитает измененное ненулевое значение из canceled.даже если это будет другое значение, а не та же запись первого потока: например, если cancelled = true переведено на mov cancelled, -1; mov cancelled, 1 - два аппаратных, не атомарных операции - второй поток может прочитать -1 вместо конечного 1 (true) от отменено, но это не играет роли, если мы проверяем только ненулевое значение - все другие значения прерывают цикл - while (!*cancelled);, если мы используем здесь атомарную операцию для записи / чтения cancelled - здесь ничего не меняется - после одного потока атомарной записик нему другой поток рано или поздно будет читать измененное ненулевое значение из отмененной - атомарной операции или нет - память является общей - если один поток записывает в память (атомарный или нет), другой поток рано или поздно будет просматривать эту модификацию памяти.

  • нам нужно синхронизировать еще одно чтение / запись с отмененным.поэтому нам нужна точка синхронизации между двумя потоками вокруг canceled с порядком памяти, отличным от memory_order_relaxed, скажем, например, следующий код:

//

void _do_task();

int result;

void do_task(atomic_bool *cancelled)
{
    do {
        _do_task();
    } while (!g_cancelled.load(memory_order_acquire));

    switch(result)
    {
    case 1:
        //...
        break;
    }
}

void some_proc()
{
    result = 1;
    g_cancelled.store(true, memory_order_release);
}

, поэтому мы не простоустановите g_cancelled в значение true, но перед этим
запишите некоторые общие данные (result) и захотите, чтобы другой поток после модификации представления g_cancelled также просмотрел модификацию общих данных
(result),но я сомневаюсь, что вы действительно используете / нуждаетесь в этом
сценарии

, если ни одна из этих 3 вещей не нужна - вам не нужно атомное здесь.что вам действительно нужно - чтобы один поток просто записал true в cancelled, а другой поток все время читал значение cancelled (вместо этого сделайте это один раз и кешируйте результат).как правило, в большинстве случаев кода это будет сделано автоматически, но для того, чтобы быть точным, вам нужно объявить отмененный как volatile

, если, тем не менее, по какой-то причине вам нужен именно атомарный (atomic_bool), потому чтоВы пересекаете границу языков, вам необходимо понимать конкретную реализацию atomic_bool на обоих языках и одинаковы ли они (объявление типа, операции (загрузка, хранение и т. д.)).на самом деле atomic_bool одинаково для c и c ++ .

или (лучше) вместо сделать видимым и поделиться типом atomic_bool использовать функции интерфейса, такие как

bool is_canceled(void* cancelled);

, поэтому код может быть следующим

// c code
void _do_task();

bool is_canceled(void* cancelled);

void do_task(void *cancelled)
{
    do {
        _do_task();
    } while (!is_canceled(cancelled));
}

// c++ code

atomic_bool g_cancelled;// can be modify by another thread

bool is_canceled(void* cancelled)
{
    return *reinterpret_cast<atomic_bool*>(cancelled);
}

void some_proc()
{
    //...
    g_cancelled = true;
}

do_task(&g_cancelled);

но опять же я сомневаюсь, что в вашей задаче вам нужно atomic_bool по смыслу.вам нужно volatile bool

0 голосов
/ 23 декабря 2018

Чтобы обойти все проблемы ABI, вы можете реализовать функцию C, которая вызывается из C ++ и работает с этим atomic_bool.Таким образом, ваш код C ++ не должен ничего знать об этой глобальной переменной и ее типе:

В файле .h:

#ifdef __cplusplus
extern "C" {
#endif

void cancel_my_thread(void);
int is_my_thread_cancelled(void);

#ifdef __cplusplus
}
#endif

А затем в файле .c:

#include <stdatomic.h>

static atomic_bool cancelled = 0;

void cancel_my_thread(void) {
    atomic_store_explicit(&cancelled, 1, memory_order_relaxed);
}
int is_my_thread_cancelled(void) {
    return atomic_load_explicit(&cancelled, memory_order_relaxed);
}

Код C ++ будет включать заголовок и вызывать cancel_my_thread.

0 голосов
/ 22 декабря 2018

Тип atomic_bool в C и тип std::atomic<bool> в C ++ (определен как std::atomic_bool) - это два разных типа, которые не связаны между собой.Передача std::atomic_bool функции C, ожидающей, что C atomic_bool - это неопределенное поведение.То, что это работает вообще, - это комбинация удачи и совместимости простых определений этих типов.

Если код C ++ должен вызывать функцию C, которая ожидает C atomic_bool, то это то, что он должен использовать,Однако заголовок <stdatomic.h> не существует в C ++.Вы должны будете предоставить способ для кода C ++ вызывать код C, чтобы получить указатель на атомарную переменную, которая вам нужна, таким образом, чтобы скрыть тип.(Возможно, объявите структуру, которая содержит атомарный тип bool, что C ++ будет знать только, что тип существует, и знать только об указателях на него.)

0 голосов
/ 23 декабря 2018

Я нашел это в поиске по сети https://developers.redhat.com/blog/2016/01/14/toward-a-better-use-of-c11-atomics-part-1/

Следуя примеру C ++, наряду с моделью памяти, описывающей требования и семантику многопоточных программ, стандарт C11 принял предложение онабор атомарных типов и операций на языке.Это изменение позволило написать переносное многопоточное программное обеспечение, которое эффективно управляет объектами неделимо и без гонок данных.Атомарные типы полностью совместимы между двумя языками, так что можно разрабатывать программы, которые совместно используют объекты атомарных типов через границы языка.В этой статье рассматриваются некоторые из компромиссов дизайна, указываются некоторые из его недостатков и описываются решения, упрощающие использование атомных объектов на обоих языках.

Я только сейчас изучаю атомные технологии., но похоже, что он совместим между C и CPP.

EDIT

Другой источник Поддержка многопоточности в c11

0 голосов
/ 22 декабря 2018

Атомарность операций вызвана аппаратными средствами, а не программными (ну, в C ++ есть также «атомарные» переменные, которые являются только атомарными по имени, которые реализуются через мьютексы и блокировки).Итак, в основном атомы C ++ и атомы C делают одно и то же.Следовательно, пока типы совместимы, проблем не будет.А атомарные классы C ++ 11 и C11 были созданы для совместимости.


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

1) Мы начнем с основ.Что и почему атомы?Как работает память?

Модель памяти: воспринимайте процессор как несколько независимых ядер, каждое из которых имеет собственную память (обналичивает L1, L2 и L3; фактически, наличные L3 распространены, но на самом деле это не такважно).

Зачем нам нужна атомарная операция?

Если вы не используете атомики, тогда у каждого процессора может быть своя собственная версия переменной 'x', и они, как правило, не синхронизируются.Пока неизвестно, когда они будут выполнять синхронизацию с наличными в ОЗУ / L3.

Когда используются атомарные операции, используются такие операции с памятью, которые обеспечивают синхронизацию с наличными в ОЗУ / L3 (или с чем угодно), обеспечиваяядра имеют доступ к одной и той же переменной и не имеют различных ее версий.

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

2) Хорошо, а как насчет блокировок и мьютексов?

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

3) Скажем, у меня есть atomic_bool, может ли она работать взаимозаменяемо на разных языках (C / C ++ 11)?

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

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

Если вы используете atomic_bool на любом современном компьютере с C / C ++, он наверняка сможет синхронизироваться без блокировок.

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