воскресенье, 13 мая 2012 г.

Примитивы синхронизации

Представьте, что Вы должны написать многопоточную программу на языке, в котором почти нет примитивов синхронизации. Единственное средство - функция TSL (test, set and lock), которая выглядит следующим образом:
int tsl(int* value) {
   int oldValue = *value;
   *value = 1;
   return oldValue;
}
При этом среда гарантирует, что функция атомарна. Например, что компилятор транслирует её в атомарную машинную инструкцию.

Как реализовать с помощью этого примитива критическую секцию? Оптимально было бы получить реализацию двух функций: lock() и unlock().

Решение
Простейший вариант с активным ожиданием мог бы выглядеть так:
int lock = 0;

void lock() {
    while(!tsl(*lock)) {
        // nothing
    }
}

void unlock() {
    *lock = 0;
}

7 комментариев:

  1. В такой реализации unlock() не будет работать. Нужны барьеры.

    Можно представить, что tsl содержит acquire fence, но "*lock = 0;" уж точно не делает release.

    ОтветитьУдалить
  2. Собственно, это следующий вопрос, который мы задаём на собеседовании :) Жаль только, что не все до него добираются.

    ОтветитьУдалить
  3. По-моему, это вопрос на "знал - не знал". Думать тут нечего. Чем вы занимаетесь на работе, что для вас критично знание lock free алгоритмов?

    Кстати, есть еще системы, на которых присваивание интов не атомарно. На них даже барьеры не спасут.

    Задачу можно было бы переформулировать так: даны функции tsl и set_zero, нужно написать lock и unlock. После этого можно спросить, как в реальности можно реализовать tsl и set_zero и посмотреть, в курсе ли кандидат о CAS, барьерах, acquire и release семантике операций и т.п. Опять же, это предполагает, что знание lock free алгоритмов коррелирует с будущим успехом кандидата в компании, что спорно.

    ОтветитьУдалить
  4. Занимаемся тем, что торгуем на валютном рынке, предлагая клиентам купить/продать валюту или производные инструменты.

    Грубо говоря, надо посмотреть текущий курс на бирже, предложить клиенту курс на десятую копейки в нашу сторону, а когда клиент заключит с нами сделку - бежать на биржу, пока разница в десятую копейки не превратилась в 0 или в минус 2 копейки.

    Клиентов много (тысячи), валютных пар тоже много (несколько сотен), работать надо быстро. Вот и получается, что без хорошего знания concurrency человеку будет сложно влиться в проект.

    ОтветитьУдалить
  5. Все работает.
    Непонятно при чем тут барьеры.
    Только не *lock = 0; , а lock = 0; ,а то по защите вылетите.
    Другое дело при такой организации порядок вхождения в критическую секцию детерминирован - поток может вечно "не успевать".

    ОтветитьУдалить
  6. *недетерминирован (спеллчекер не сьел).

    ОтветитьУдалить
  7. Барьеры тут нужны.

    int shared_variable;

    lock();
    shared_variable = 42;
    unlock();

    Если unlock() не содержит release fence, side effects двух последних строчек могут произойти в обратном порядке. Что на практике будет выглядеть вот так:

    lock();
    unlock();
    shared_variable = 42;

    Упс.

    *lock = 0 не вылетит по защите, а просто не скомпилируется. Даже если убрать звездочку, все равно не скомпилируется.

    ОтветитьУдалить