Страницы

Поиск по вопросам

Показаны сообщения с ярлыком lock-free. Показать все сообщения
Показаны сообщения с ярлыком lock-free. Показать все сообщения

четверг, 2 января 2020 г.

Блокирование и разблокировка файла

#cpp #c #файлы #lock_free #flock


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

int Writer()
{
    int iErrorCode = 0;
    char pcStr[] = "I am string, that must be written to file.";

    int iTry = 0;
    int iMaxTry = 10;

    struct flock lck;
    lck.l_type = F_WRLCK; /*setting a write lock*/
    lck.l_whence = 0;     /*offset l_start from beginning of file*/
    lck.l_start = 0l;
    lck.l_len = 0l;       /*until the end of the file address space*/

    int fd;   /*file descriptor*/
    fd = open("outfile.txt", O_RDWR);
    if (fd < 0) {
        perror("outfile.txt");
        return 1;
    }

    while (fcntl(fd, F_SETLK, &lck) < 0) {
        if (errno == EAGAIN || errno == EACCES) {
            if (++iTry < iMaxTry) {
                sleep(2);
                continue;
            }
            (void) fprintf(stderr, "File is bisy.");
        }
    }

    size_t read_bytes;
    size_t written_bytes;
    size_t szContentLen = strlen(pcStr);
    char *buffer = new char[szContentLen];
    strcat(buffer, pcStr);
    while ((read_bytes = read (fd, buffer, szContentLen)) > 0)
    {
        /* 1 == stdout */
        written_bytes = write (1, buffer, read_bytes);
        if (written_bytes != read_bytes)
        {
            fprintf (stderr, "Cannot write\n");
            return 2;
        }
    }
    if (close (fd) != 0)
    {
        fprintf (stderr, "Cannot close file (descriptor=%d)\n", fd);
        return 3;
    }

    return iErrorCode;
}


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


Ответы

Ответ 1



Разблокировать файл после окончания записи можно так же, как вы его блокировали, только параметр в структуре поменять: lck.l_type = F_UNLCK; /*unlock file */ lck.l_whence = 0; /*offset l_start from beginning of file*/ lck.l_start = 0l; lck.l_len = 0l; /*until the end of the file address space*/ fcntl(fd, F_SETLK, &lck);

среда, 25 декабря 2019 г.

C++ реализация call_once

#cpp #mutex #lock_free


Хочу разобраться в том, как работает std::call_once. И главное - lock-free ли он.
Здесь пытаются его реализовать с использованием мьютекса. Если call_once можно реализовать
только с использованием мьютекса, какие проблемы могут возникнуть с этим кодом?

#include 
#include 
#include 

using namespace std;
using my_once_flag = atomic;

void my_call_once(my_once_flag& flag, std::function foo) {
    bool expected = false;
    bool res = flag.compare_exchange_strong(expected, true,
                                            std::memory_order_release, std::memory_order_relaxed);
    if(res)
        foo();
}
my_once_flag flag;
void printOnce() {
    my_call_once(flag, [](){
       cout << "test" << endl;
    });
}
int main() {
    for(int i = 0; i< 50; ++i){
            thread([](){
                printOnce();
            }).detach();
    }
    return 0;
}

    


Ответы

Ответ 1



Стандарт не налагает ограничений на реализацию std::call_once поэтому, его реализация может быть как с блокировками, так и без(я не знаю, возможно ли такую реализацию придумать). Что касается Вашей реализации: она просто неверна. Пусть у нас будет 2 потока, которые одновременно заходят в функцию и попадают на строчку: flag.compare_exchange_strong один из них выставит флаг, а другой уйдёт с полной уверенностью, что функция уже была вызвана. Но до вызова функции дело ещё вообще не дошло! Поэтому, в правильной реализации, на входе в call_once все потоки должны выстроиться в очередь, если кто-то уже начал выполнение функции. Конечно, если бы функция была «чистой»(pure) можно было бы устроить спекулятивное выполнение, с принятием результата от того потока, что первым закончит её исполнение. Но стандарт не налагает никаких ограничений на функцию, которая может быть исполнена в call_once. Поэтому, в целом, я не вижу как можно её реализовать в неблокирующем виде.

понедельник, 23 декабря 2019 г.

Варианты реализации lock-free алгоритма

#cpp #c #lock_free


Допустим, есть класс:

class LockFree 
{
   LockFree(int num) : n(num) {}
   int next() {n += 1; return n;}
private:
   int n;
};


Какие есть стандартные методы организовать lock-free доступ к n?
То есть, интересует lock-free (без мьютексов) реализация функции next(), которую
можно было бы использовать в многопоточной многоядерной системе.

Было бы круто узнать как о возможностях современного стандарта (вероятно это std::atomic
+ варианты compare_exchange), так и о самопальных велосипедах, которые можно было бы
использовать в plain c.
    


Ответы

Ответ 1



Самый простой метод: class LockFree { LockFree(int num) : n(num) {} int next() {return ++n;} private: std::atomic n; }; Это покроет все Ваши нужды в 99% случаев. а если next() будет вида next() { return n + n^2 + n^3; } Тогда будет так: int next() { int oldN = n; while(true) { int newN = oldN + oldN*oldN + oldN*oldN*oldN; if(n.compare_exchange_strong(oldN, newN)) return newN; } }

Ответ 2



Больше чтобы не забыть, вариант ответа от ixSci на С: #include int next(atomic_int *n) { int oldN = atomic_load(&n); while (1) { int newN = oldN + oldN*oldN + oldN*oldN*oldN; if (atomic_compare_exchange_strong(n, &oldN, newN)) { return newN; } }; }

вторник, 10 декабря 2019 г.

Взаимодействие рабочих потоков с GUI

#gui #winforms #проектирование #lock_free #многопоточность


Интересуют реализации взаимодействия рабочих потоков с GUI со стороны рабочих потоков.
Например, загрузка файла с сервера выполняется в выделенном потоке. Этот поток должен
сообщать юзеру о прогрессе загрузки через какой-нибудь прогрессбар. Но доступ к прогрессбару
имеет только поток, владеющий родительским окном.
В .NET часто используются методы Control.Invoke() и Control.BeginInvoke()/Control.EndInvoke().
Этот вариант вполне годится, когда одна итерация фоновой задачи требует значительно
большего времени, чем отображение прогресса. Но он превращается в толстую прибавку
к длительности выполнения фоновой задачи, когда итераций много и время выполнения каждой
такой итерации сравнима с временем, которое рабочий поток тратит на уведомление о прогрессе.
Я в таком случае добавляю коллбек в lockfree-очередь и с помощью PostMessage уведомляю
об этом владеющий окном поток. В переопределенном методе WndProc последовательно выбираю
и выполняю все коллбеки из очереди. Таким образом, накладные расходы сводятся к минимуму.
Какие подходы применяете вы? Платформа и язык не важны, будут интересны все случаи.    


Ответы

Ответ 1



Я в таком случае добавляю коллбек в lockfree-очередь и с помощью PostMessage уведомляю об этом владеющий окном поток. В переопределенном методе WndProc последовательно выбираю и выполняю все коллбеки из очереди. Таким образом, накладные расходы сводятся к минимуму. У Вас самый хороший вариант. Только я бы добавил ограничение по времени на период опроса очереди в WndProc, т.е. если lockfree-очередь не заканчивается к примеру через 100мс - завершать обработку текущего сообщения принудительно, чтобы избежать подвисаний GUI. Миграция комментариев: Я думаю, на самом деле такую проверку даже в WndProc делать нежелательно, поскольку это означает, что на каждый элемент очереди синхронизации в очереди потока висит сообщение, посланное post'ом. -- Чтобы избежать холостой перегрузки очереди потока, лучше вырабатывать очередь lockfree не по сообщению, а по событию (неавтомату [или семафору, в роли дросселя, это более безопасно]) в каком-нибудь отдельном потоке, который выбрав из очереди синхронизации, к примеру 100мс-узлов - отправит один post в очередь потока с этим, уже выбранным списком. -- 100мс-узлов - это число узлов, выбранное из очереди за не более 100мс. -- Вобщем-то, в таком случае, очередь безболезненно заменяется на простой стек lockfree: push - в прямом порядке, pop - в обратном, но последний push, который формирует очередь 100мс-узлов, опять вернет прямой порядок узлов. -- Могу только опять заметить, что даже в такой ситуации от подвисания GUI гарантий нет, т.к. мы ничего не знаем, что творится в самих коллбеках. Но вот этот момент и можно уже ограничивать непосредственно в WndProc, отправляя "слишком долгие коллбеки" на повторный круг - аналог post'а, только в очереди синхронизации. -- После этого, повесить GUI сможет только уже один конкретный колбек, но это уже проблема программиста, который решил все впихнуть в одну лямбду, к нему и претензии. Дискуссия: В ста миллисекундах 100000000 наносекунд; если задержка в десяток наносекунд, происходящая раз в 100000000 наносекунд, мешает вашему алгоритму, что же говорить про context switch? Во первых, @VladD, я не говорил о задержке раз в 100мс, а во-вторых, не говорил, что она мешает. Она меняет логику работы алгоритма, работающего на lock-free-скоростях. -- У меня есть такое понятие, как "ожидание свободного ресурса", т.е., если через какой-то промежуток времени из стека lock-free не будет извлечен ресурс, то создается новый. Вот я и нашел такой временнОй диапазон, который влияет на итоговый объем задействованного ресурса задачей. К примеру: при ожидании ресурса более 30нс - итоговое число ресурсов примерно ограничено числом ядер(в моем случае - ~7-8), независимо от числа подзадач(~30-70 штук), его использующих, а вот на 10нс, это число составляет примерно 70% от числа подзадач. Конечно, такие задержки можно делать только на основе показаний TSC, о чем я сразу и сказал. Вы уверены, что с вашим кодом всё в порядке? Поверьте, у меня было достаточно времени его проверить :) Или вы всё же немного приукрасили для большей выпуклости аргумента? А Вы сами в это можете поверить? У меня хорошая репутация, зачем мне ее портить?

Ответ 2



Я лично пользуюсь Dispatcher.BeginInvoke в WPF (аналог Control.BeginInvoke). Низкоуровневые штуки типа PostMessage кажутся мне хаком, никто не гарантирует, что окна в WPF всегда будут основаны на WinAPI-шных окнах (Silverlight? WinRT?). Обновлять UI так часто, чтобы аж затраты на обновление были существенны, не имеет смысла, так как всё равно юзер не в состоянии осознать столь быстрые изменения. Поэтому я ограничиваю снизу время между последовательными обновлениями прогресса, скажем, эмпирической сотней миллисекунд. Kонцептуально более правильным решением было бы использование Rx Extensions для push-нотификаций, но я пока не научился хорошо с ними работать.

понедельник, 14 января 2019 г.

Блокирование и разблокировка файла

Есть программа, которая перезаписывает некоторый файл. И есть другая программа, которая периодически считывает данные из файла. Однако, чтобы читающая программа всегда получала верные данные, нужно блокировать файл, пока он не запишется до конца. Есть такой код для записи файла с блокировкой:
int Writer() { int iErrorCode = 0; char pcStr[] = "I am string, that must be written to file.";
int iTry = 0; int iMaxTry = 10;
struct flock lck; lck.l_type = F_WRLCK; /*setting a write lock*/ lck.l_whence = 0; /*offset l_start from beginning of file*/ lck.l_start = 0l; lck.l_len = 0l; /*until the end of the file address space*/
int fd; /*file descriptor*/ fd = open("outfile.txt", O_RDWR); if (fd < 0) { perror("outfile.txt"); return 1; }
while (fcntl(fd, F_SETLK, &lck) < 0) { if (errno == EAGAIN || errno == EACCES) { if (++iTry < iMaxTry) { sleep(2); continue; } (void) fprintf(stderr, "File is bisy."); } }
size_t read_bytes; size_t written_bytes; size_t szContentLen = strlen(pcStr); char *buffer = new char[szContentLen]; strcat(buffer, pcStr); while ((read_bytes = read (fd, buffer, szContentLen)) > 0) { /* 1 == stdout */ written_bytes = write (1, buffer, read_bytes); if (written_bytes != read_bytes) { fprintf (stderr, "Cannot write
"); return 2; } } if (close (fd) != 0) { fprintf (stderr, "Cannot close file (descriptor=%d)
", fd); return 3; }
return iErrorCode; }
Как разблокировать данный файл после окончания записи, чтобы другая программа, которая захочет читать из файла, имела доступ к нему?


Ответ

Разблокировать файл после окончания записи можно так же, как вы его блокировали, только параметр в структуре поменять:
lck.l_type = F_UNLCK; /*unlock file */ lck.l_whence = 0; /*offset l_start from beginning of file*/ lck.l_start = 0l; lck.l_len = 0l; /*until the end of the file address space*/
fcntl(fd, F_SETLK, &lck);

четверг, 29 ноября 2018 г.

C++ реализация call_once

Хочу разобраться в том, как работает std::call_once. И главное - lock-free ли он. Здесь пытаются его реализовать с использованием мьютекса. Если call_once можно реализовать только с использованием мьютекса, какие проблемы могут возникнуть с этим кодом?
#include #include #include
using namespace std; using my_once_flag = atomic;
void my_call_once(my_once_flag& flag, std::function foo) { bool expected = false; bool res = flag.compare_exchange_strong(expected, true, std::memory_order_release, std::memory_order_relaxed); if(res) foo(); } my_once_flag flag; void printOnce() { my_call_once(flag, [](){ cout << "test" << endl; }); } int main() { for(int i = 0; i< 50; ++i){ thread([](){ printOnce(); }).detach(); } return 0; }


Ответ

Стандарт не налагает ограничений на реализацию std::call_once поэтому, его реализация может быть как с блокировками, так и без(я не знаю, возможно ли такую реализацию придумать).
Что касается Вашей реализации: она просто неверна. Пусть у нас будет 2 потока, которые одновременно заходят в функцию и попадают на строчку: flag.compare_exchange_strong один из них выставит флаг, а другой уйдёт с полной уверенностью, что функция уже была вызвана. Но до вызова функции дело ещё вообще не дошло! Поэтому, в правильной реализации, на входе в call_once все потоки должны выстроиться в очередь, если кто-то уже начал выполнение функции.
Конечно, если бы функция была «чистой»(pure) можно было бы устроить спекулятивное выполнение, с принятием результата от того потока, что первым закончит её исполнение. Но стандарт не налагает никаких ограничений на функцию, которая может быть исполнена в call_once. Поэтому, в целом, я не вижу как можно её реализовать в неблокирующем виде.

среда, 21 ноября 2018 г.

Варианты реализации lock-free алгоритма

Допустим, есть класс:
class LockFree { LockFree(int num) : n(num) {} int next() {n += 1; return n;} private: int n; };
Какие есть стандартные методы организовать lock-free доступ к n? То есть, интересует lock-free (без мьютексов) реализация функции next(), которую можно было бы использовать в многопоточной многоядерной системе.
Было бы круто узнать как о возможностях современного стандарта (вероятно это std::atomic + варианты compare_exchange), так и о самопальных велосипедах, которые можно было бы использовать в plain c.


Ответ

Самый простой метод:
class LockFree { LockFree(int num) : n(num) {} int next() {return ++n;} private: std::atomic n; };
Это покроет все Ваши нужды в 99% случаев.

а если next() будет вида next() { return n + n^2 + n^3; }
Тогда будет так:
int next() { int oldN = n; while(true) { int newN = oldN + oldN*oldN + oldN*oldN*oldN; if(n.compare_exchange_strong(oldN, newN)) return newN; } }