LINUX.ORG.RU

[многопоточность] Двухуровневые блокировки


0

1

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

int log (int priority, Facility *f, const char *msg, ...)
{
   pthread_mutex_lock (&f->mutex);
   ... // записать сообщение
   pthread_mutex_unlock (&f->mutex);
   ...
}
Хочется теперь добавить возможность вручную получить в клиентском коде блокировку записи в лог на некоторое время, чтобы иметь возможность добавить несколько сообщений, которые гарантированно идут подряд:
log_write_lock (f);
  log (LOG_INFO, f, "несколько");
  log (LOG_INFO, f, "сообщений");
  log (LOG_INFO, f, "подряд");
log_write_unlock (f);
Как бы это получше сделать? Очевидный вариант - использовать мьютекс с атрибутом PTHREAD_MUTEX_ERRORCHECK и дополнительный флаг manually_locked, и снимать блокировку, только если этот флаг не установлен. При этом все вызовы pthread_mutex_lock до снятия вручную блокировки будут просто возвращать EDEADLK и все ок. Проблема в том, что такие мьютексы тормозные и непереносимые.


поток который будет обслуживать сообщение на основании очереди типа FIFO ?

Jetty ★★★★★ ()
Ответ на: комментарий от Jetty

много гемора, придется делать очередь с приоритетами - надо ведь будет отложить сообщения от других потоков, если установлена ручная блокировка. Ну и плюс придется как-то передавать сообщения, а это копирования, динамическое выделение памяти и пр. нехорошие вещи.

gaga ()

man pthread_mutexattr_settype

см. PTHREAD_MUTEX_RECURSIVE

Sorcerer ★★★★★ ()
#define log(priority, f,  msg, ...)
{
   pthread_mutex_lock (&f->mutex);
   __log(priority, f, ## __VA_ARGS__);
   pthread_mutex_unlock (&f->mutex);
}

int main()
{
  pthread_mutex_lock (&f->mutex);
  __log (LOG_INFO, f, "несколько");
  __log (LOG_INFO, f, "сообщений");
  __log (LOG_INFO, f, "подряд");
  pthread_mutex_unlock (&f->mutex);
}
ttnl ★★★★★ ()
Ответ на: комментарий от gaga

не вижу ничего страшного в этом... Кроме того, если чесно, идея сообщений подряд в ЛОГАХ мне совсем не понятна. Введите простое идентифицирующее поле в логе и фильтрация/выборка становится в разы проще :)

Jetty ★★★★★ ()
Ответ на: комментарий от Sorcerer

я думал, что рекурсивные мьютексы тоже непереносимые? Это не так?

gaga ()
Ответ на: комментарий от ttnl

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

gaga ()
Ответ на: комментарий от Jetty

не вижу ничего страшного в этом

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

gaga ()
Ответ на: комментарий от gaga

Мне кажется ты что-то непонятное изобретаешь :)

Jetty ★★★★★ ()
Ответ на: комментарий от Jetty

ну в принципе да )
требования:
- потенциальное реальное время (при условии риалтаймого fprintf или его аналогов)
- потокобезопасность
- простота и малый размер
- возможность сборки без pthreads (небезопасная, но все равно должна работать)
- (бонус) syslog в качестве бакэнда (на целевой машине его, правда, нет)

gaga ()

Сделать буферизацию сообщений.

log_begin_block (f); // Кладем дальнейшие сообщения в буфер данного потока без блокировки
log (LOG_INFO, f, "несколько");
log (LOG_INFO, f, "сообщений");
log (LOG_INFO, f, "подряд");
log_end_block (f); // Делаем блокировку и пишем в файл
Deleted ()
Ответ на: комментарий от Deleted

придется выделять под буфер память динамически и потом освобождать, это плохо.

gaga ()
Ответ на: комментарий от gaga

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

Deleted ()
Ответ на: комментарий от Deleted

а я хз сколько нужно)
на самом деле, я почитал, действительно, рекурсивные мьютексы описаны как минимум в 2001 посиксе, так что буду просто использовать их пока что, это проще всего. Не люблю я, правда, их, потому что сломать очень легко. Я видимо попутал их с errorcheck, который пока считается не особо переносимым.

gaga ()

Очередь и сообщения надо.

А так что получается, твой поток, который хочет записать в лог, должен ждать других, которые уже пишут? Тебе не кажется, что это типичный bottle-neck?

Представь: допустим лог пишется по сети (или на медленный носитель), у тебя 100500 потоков и все хотят записать в него.

invy ★★★★★ ()

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

unC0Rr ★★★★★ ()

Пишу простую велосипедную библиотеку для ведения логов из программы.

Поттеринг, перелогиньтесь!

geekless ★★ ()
Ответ на: комментарий от invy

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

gaga ()
Ответ на: комментарий от gaga

При использовании очереди потоки не ждут (почти, зависит от реализации). Ждёт логгер.
А последовательная запись, выше уже посоветовали: добавлять в лог название потока, pid, timestamp.

А читать grep pid и вот вся последовательная запись.

invy ★★★★★ ()
Ответ на: комментарий от gaga

Ну зачем вам эти мутексы?! У вас же потоки будут блокироваться, пока лог пишется.

Deleted ()
Ответ на: комментарий от Deleted

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

gaga ()

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

note173 ★★★★★ ()
Ответ на: комментарий от note173

если в с/c++ упадет поток - упадет все. так что можно не париться)

gaga ()
Ответ на: комментарий от gaga

не упадёт, а зависнет :)
и зависнет всё...

invy ★★★★★ ()
Ответ на: комментарий от gaga

Исключение может где-нибудь кинуться, а поток его где-нибудь у себя наверху обработает... Но для с++ это не так принято, да.

note173 ★★★★★ ()
Ответ на: комментарий от invy

не упадёт, а зависнет :)

что значит зависнет, куда это поток зависнет?

shty ★★★★★ ()

Очевидный вариант - использовать мьютекс с атрибутом

очевидный вариант - собрать очередь сообщений у себя, и вызывать лог уже на очереди

и я вообще не понимаю, риалтайм лог - это что, и нафига?

shty ★★★★★ ()
Ответ на: комментарий от shty

риалтайм лог - это что, и нафига

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

gaga ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.