Arhn - архитектура программирования

Есть ли функция TestAndSet(volatile int *lock) в общей библиотеке C?

... или я должен написать свой собственный? (Кстати, я работаю на C)

Я пишу реализацию, похожую на то, что есть в википедии:

volatile int lock = 0;

void Critical() {
    while (TestAndSet(&lock) == 1);
    critical section // only one process can be in this section at a time
    lock = 0 // release lock when finished with the critical section
}

Но я не могу найти готовый TestAndSet(volatile int *lock).

Примеры того, как они могут выглядеть, включают:

#define LOCKED 1
int TestAndSet(volatile int* lockPtr) {
    int oldValue;
    oldValue = *lockPtr;
    *lockPtr = LOCKED;
    return oldValue;
}

В идеале я хотел бы что-то, что может работать как на Linux, так и на Windows. Кроме того, я читал, что выполнение атомарных инструкций зависит от аппаратного обеспечения. Я не уверен, что это играет роль или как определить, поддерживает ли это оборудование, а затем запустить альтернативу.

Благодарю вас!

Дополнительная контекстная информация. Причина, по которой я задаю этот вопрос, заключается в разработке набора функций для доступа к структуре данных (например, add() fetch() delete() и т. д.). Несколько потоки обращаются к нему для модификации и отображения определенных элементов в реальном времени.

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

Альтернатива: я обратил внимание на TestAndSet() потому, что имело больше смысла помещать флаг «BeingAccessed» для каждого элемента в структуре данных. Этот флаг проверяется функцией, которая хочет получить к нему доступ, устанавливается в значение true, если оно ложно, а затем функция делает то, что должна, а затем освобождает этот элемент, не замораживая всю структуру.

Комментарий @M.M: Пример реализации показался неправильным по причине, которую @chux и вы оба упомянули. Насколько я понимаю, занятое ожидание используется на низком уровне для разработки механизмов синхронизации более высокого уровня. Пожалуйста, смотрите мое редактирование выше: мьютексы. Волатильность не была попыткой обеспечить атомарность, но чтобы гарантировать, что значение загружается каждый раз, когда к нему обращаются, когда оно проверяется атомарной функцией, потому что несколько потоков могут изменить эту переменную в любое время. Атомарность, которую я представляю/надеюсь, обеспечивается функцией, действующей на рассматриваемую переменную. Вопрос, относящийся к тому, что вы написали: ваш код говорит: «Примечание: не используйте «изменчивый»», но предоставленный вами стандартный прототип функции является изменчивым, поэтому переменная флага энергонезависимости приведена как изменчивая в атомарной функции? Спасибо.


  • Н.Б. Я добавил изменчивую часть. Этого не было в исходном примере, который я нашел. 04.03.2016
  • Между oldValue = *lockPtr; и *lockPtr = LOCKED; что мешает *lockPtr измениться? 04.03.2016
  • @chux - я знаю, верно? Вот почему я не уверен, что я что-то упустил или, возможно, есть свойство указателей, о котором я не знаю. 04.03.2016
  • @ J-a-n-u-s указатель с квалификацией volatile означает, что функция может работать как с энергозависимыми, так и с энергонезависимыми флагами. (аналогично тому, как функция, принимающая const char *, может также передавать неконстантный массив символов). Как объяснено в моем ответе, поскольку несколько потоков могут получить доступ к переменной, это не означает, что переменная должна быть объявлена ​​​​как изменчивая. 05.03.2016

Ответы:


1

В стандарте C11 ваш флаг будет выглядеть так:

#include <stdatomic.h>

// Lock-free atomic flag, initially unset. Note: do not use "volatile"
atomic_flag lock = ATOMIC_FLAG_INIT;

и есть стандартные функции:

_Bool atomic_flag_test_and_set(volatile atomic_flag *object);

который выполняет требуемую операцию.

Обратите внимание, что ваш образец реализации недействителен, поскольку флаг может быть изменен другим потоком между чтением и записью lock_ptr. Я тестировал с clang 3.7 и gcc 5.x, и они реализовали atomic_flag_test_and_set как инструкцию по сборке XCHG.


Кроме того, ваша функция Critical реализует «спин-блокировку», т.е. она будет максимально загружать ЦП, постоянно пытаясь изменить флаг. Это хорошая идея только в тех случаях, когда блокировка будет удерживаться только в течение нескольких циклов ЦП. Дополнительные сведения по этой теме см. в этой теме.

Для обычного использования, когда код, защищенный критической секцией, может выполнять системный вызов, было бы предпочтительнее использовать средство операционной системы для ожидания флага незанятости.

Библиотека потоков C11 включает мьютекс. По состоянию на 2015 год, по-видимому, нет основного компилятора Комбинация /library изначально поддерживает поток C11, хотя, как было сказано в этом потоке, существует проект github, который реализует потоки C11 поверх потоков POSIX.

Конечно, вы можете использовать проверенную временем технику #if для выбора CRITICAL_SECTION в Windows и мьютекс библиотеки pthread во всех других ОС.


Об использовании volatile: это не является ни необходимым, ни достаточным для многопоточности.

Этого достаточно недостаточно, поскольку он не гарантирует атомарности. Неустойчивый объект может по-прежнему считываться/записываться одновременно несколькими потоками (гонка данных, UB). Вы должны использовать атомарные объекты либо с помощью C11 Atomics, либо с помощью специфичной для компилятора атомарной функции.

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

volatile sig_atomic_t рекомендовался в C99 как своего рода хак, потому что в стандарте C99 не было никакого понятия о заборе памяти. Однако, продвигаясь вперед, теперь, когда доступны настоящие атомы, изменчивый хак должен быть отправлен в кучу мусора.

04.03.2016
  • Хорошее объяснение недостаточности volatile. 04.03.2016
  • @ M.M - Здесь недостаточно места для моего комментария, поэтому я поместил свой комментарий во второе редактирование своего вопроса. 04.03.2016
  • И вы, и @Collin Dauphinee очень помогли. Теперь у меня есть камень преткновения, который заключается в том, что мой atomic_flag является частью определения структуры, и с ATOMIC_FLAG_INIT там он, кажется, перезаписывает данные в структуре, следующей за ним. Я думаю, что это может быть связано с выравниванием/необходимостью заполнения, но я не уверен, как это сделать. Любые идеи? 08.03.2016

  • 2

    C11 содержит новую атомарную библиотеку, которая обеспечивает эту функциональность. См. функции atomic_flag_test_and_set; просто замените int* на atomic_flag*

    Вы можете просто использовать функции mutex (mtx_*), предоставленные в threads.h C11, вместо запускаете собственную синхронизацию.

    04.03.2016
  • Спасибо за помощь. Пожалуйста, смотрите мой комментарий под ответом @MM для дополнительного вопроса. У вас обоих был правильный ответ, но дополнительный пример кода, который он предоставил, был очень полезен. Я ценю, что вы нашли время. Спасибо еще раз. 08.03.2016

  • 3

    Это часто делается с помощью встроенного языка ассемблера или расширений компилятора. Например, вы можете использовать атомарные функции GCC в и линукс и винда. Компилятор Microsoft C может иметь что-то эквивалентное.

    04.03.2016

    4

    В Linux (и вообще во всех *nix-ах) вы можете использовать pthread:

    #include <pthread.h>
    pthread_mutex_t mutex;
    void init_mutex() {
        pthread_mutex_init(&mutex, NULL);
    }
    void lock_mutex() {
        pthread_mutex_lock(&mutex);
    }
    void release_mutex() {
        pthread_mutex_unlock(&mutex);
    }
    void delete_mutex() {
        pthread_mutex_destroy(&mutex);
    }
    

    Можно, конечно, получить указатель на mutex и привести его к int*, но это бессмысленно! Эти четыре функции выполняют:

    • init_mutex - Вызов при запуске программы, ресурсы инициализации могут быть вызваны даже до main (отметьте это __attribute__((constructor)) перед ключевым словом "void".
    • delete_mutex - Вызвать окончание программы, освободить ресурсы.
    • lock_mutex — заблокирует вашу блокировку, не позволяя другим потокам получить доступ к этой функции (нет возможности ограничить ее между процессами).
    • release_mutex — вызовите его, чтобы сообщить среде выполнения, что критический раздел завершен и готов принять следующий поток.

    Эти функции будут строить очередь потоков, ожидающих мьютекса. См. этот пример (шесть потоков):

    A -> B -> C -> D -> E -> [MUTEX] -> F (critical) -> [MUTEX END]
    [F] release_mutex
    A -> B -> C -> D -> [MUTEX] -> E (critical) -> [MUTEX END] -> F
    

    Я не знаю, как это сделать в Windows. если вы думаете о чем-то общем для Linux и Windows, это нехорошо. Если вы думаете об оборудовании, я должен сказать НЕТ! Потоки управляются ядром (иногда библиотекой C).

    03.03.2016
  • Спасибо за ответ. Я добавил дополнительную информацию в свой вопрос, объясняющую, почему я отказался от мьютекса. 04.03.2016
  • Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..