лучшая практика getter и сеттеров с mutex - PullRequest
2 голосов
/ 04 февраля 2020

Я чувствую себя немного ошеломленным, когда использую несколько потоков во встроенном программировании, так как каждый разделяемый ресурс заканчивается геттером / сеттером, защищенным мьютексом.

Мне бы очень хотелось понять, получаю ли геттер следующего вида

static float static_raw;
float get_raw() {
    os_mutex_get(mutex, OS_WAIT_FOREVER);
    float local_raw = static_raw;
    os_mutex_put(mutex);

    return local_raw ;
}

имеет смысл, или если присвоение float можно считать атомом c, например, для ARM (в отличие от, например, 64-битных переменных), что делает это излишним.

Я могу понять что-то вроде этого:

raw = raw > VALUE ? raw + compensation() : raw;

где значение обрабатывается несколько раз, но как насчет того, чтобы прочитать или вернуть его?

Можете ли вы прояснить мой разум?

РЕДАКТИРОВАТЬ 1: Относительно второго вопроса ниже. давайте предположим, что у нас есть «тяжелая» функция с точки зрения времени выполнения, назовем ее

void foo(int a, int b, int c)

, где a, b, c - потенциально значения из общих ресурсов. Когда вызывается функция foo, должен ли она быть обернута мьютексом, блокируя ее на длительное время, даже если ей просто нужна копия значения? например,

os_mutex_get(mutex, OS_WAIT_FOREVER);
foo(a,b,c);
os_mutex_put(mutex);

имеет ли смысл делать

os_mutex_get(mutex, OS_WAIT_FOREVER);
int la = a;
int lb = b;
int lc = c;
os_mutex_put(mutex);
foo(la,lb,lc);

блокировку только копии переменной вместо полного выполнения?

EDIT2: Учитывая, что может Существуют геттер и сеттер для "a", "b" и "c". Это проблематично c с точки зрения производительности / или чего-то еще при выполнении чего-то подобного?

int static_a;
int get_a(int* la){
    os_mutex_get(mutex, OS_WAIT_FOREVER);
    *la = static_a;
    os_mutex_put(mutex);
}

или

int static_b;
int get_b(){
    os_mutex_get(mutex, OS_WAIT_FOREVER);
    int lb = static_b;
    os_mutex_put(mutex);
    return lb;
}

, используя их как

void main(){
   int la = 0;
   get_a(&la);
   foo(la,get_b());
}

Я спрашиваю об этом, потому что я блокирую и повторно блокирую один и тот же мьютекс последовательно, без какой-либо причины.

Ответы [ 2 ]

1 голос
/ 05 февраля 2020

, если присвоение с плавающей запятой можно считать атомом c

Ничто не может считаться атомом c в C, если вы не используете C11 _Atomic или встроенный ассемблер. Базовое оборудование не имеет значения, потому что даже если слово определенного размера может быть прочитано в одной инструкции на данном оборудовании, никогда не гарантируется, что определенная команда C приведет только к одной инструкции.

имеет ли смысл делать

os_mutex_get(mutex, OS_WAIT_FOREVER);
int la = a;
int lb = b;
int lc = c;
os_mutex_put(mutex);
foo(a,b,c);

Если вы имеете в виду foo(la,lb,lc);, тогда да, это имеет большой смысл. Вот как в идеале вы должны использовать мьютекс: минимизируйте код между мьютексными блокировками, чтобы это было просто копирование необработанных переменных и ничего больше.

1 голос
/ 04 февраля 2020

Стандарт C ничего не предписывает об атомарности оператора присваивания. Вы не можете рассматривать присваивание atomi c, так как оно полностью зависит от реализации.

Однако в C11 можно использовать спецификатор типа _Atomic (C11 §6.7.3, стр. 121 здесь ) для поддержки (если поддерживается вашим компилятором) для объявите переменные для атомарного чтения и записи, чтобы вы могли, например, сделать следующее:

static _Atomic float static_raw;

float get_raw(void) {
    return static_raw;
}

Не забудьте скомпилировать с -std=c11, если вы это сделаете.


Обращаясь к вашему первому редактированию:

Когда вызывается функция foo, должна ли она быть обернута мьютексом, блокируя ее на длительное время, даже если ей просто нужна копия значения?

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

os_mutex_get(mutex, OS_WAIT_FOREVER);
int la = a;
int lb = b;
int lc = c;
os_mutex_put(mutex);
foo(la,lb,lc);

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


Обращение ко второму редактированию:

Учитывая, что могут существовать методы получения и установки для "a", "b" и "c". Является ли проблематичным c с точки зрения производительности / или чего-то еще при выполнении чего-то подобного?

Этот код правильный. С точки зрения производительности, безусловно, было бы гораздо лучше иметь один мьютекс на переменную, если вы можете. При наличии только одного мьютекса любой поток, содержащий мьютекс, будет «блокировать» любой другой поток, который пытается его заблокировать, даже если он пытается получить доступ к другой переменной.

Если вы не можете использовать несколько мьютексов, тогда это Выбор между этими двумя вариантами:

  1. Блокировка внутри геттеров:

    void get_a(int* la){
        os_mutex_get(mutex, OS_WAIT_FOREVER);
        *la = static_a;
        os_mutex_put(mutex);
     }
    
    void get_b(int* lb){
        os_mutex_get(mutex, OS_WAIT_FOREVER);
        *lb = static_b;
        os_mutex_put(mutex);
     }
    
    /* ... */
    
    int var1, var2;
    get_a(&var1);
    get_b(&var2);
    
  2. Блокировка вне геттеров (оставьте обязанность вызывающая сторона):

    int get_a(void){
        return static_a;
     }
    
    int get_b(void){
        return static_b;
     }
    
    /* ... */
    
    os_mutex_get(mutex, OS_WAIT_FOREVER);
    int var1 = get_a();
    int var2 = get_b();
    os_mutex_put(mutex);
    

    На этом этапе вам даже не понадобятся геттеры, и вы можете просто сделать:

    os_mutex_get(mutex, OS_WAIT_FOREVER);
    int var1 = a;
    int var2 = b;
    os_mutex_put(mutex);
    

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

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

Невозможно заранее сказать, какое решение является лучшим, вам следует запустить различные тесты и посмотреть, что лучше для ваш сценарий.

...