#multithreading #logging #tcl
#многопоточность #ведение журнала #tcl
Вопрос:
Мне нужна библиотека ведения журнала для моего многопоточного приложения Tcl. Могу ли я использовать стандартный logger
пакет? Если я могу, какие ограничения применяются в многопоточной среде?
Я хотел бы разделить службы ведения журнала между потоками, если это возможно.
Спасибо
Комментарии:
1. tcllib.sourceforge.net/doc/logger.html также взгляните на code.activestate.com/lists/tcl-core/10866
2. Первая ссылка ссылается на какой-то пользовательский регистратор, который слишком плохо описан для использования. Вторая ссылка относится к стандартной библиотеке регистратора, которую я упомянул в своем вопросе. Действительно ли он потокобезопасен?
Ответ №1:
Потоки Tcl не обмениваются данными (если вы явно не используете определенные средства из пакета Thread), а вместо этого обмениваются данными посредством передачи сообщений. Таким образом, кажется, что правильным решением было бы настроить выделенный поток «logger» и просто ставить в очередь сообщения о регистрации в нем из рабочих потоков.
В противном случае спорная точка, вероятно, будет где-то в ресурсе операционной системы, используемом регистратором для фактической записи данных.
Обновление Хорошо, вот рабочий набросок того, что я на самом деле предложил реализовать:
package require Tcl 8.5
package require Thread
proc make_worker_thread {logger_id body} {
set newbody [list set ::logger $logger_id]
append newbody n {
proc ::log {severity msg} {
global logger
thread::send $logger [list ::log $severity $msg]
}
} n $body
thread::create $newbody
}
set logger [thread::create {
package require logger
proc log {severity msg} {
puts "hey, that's it: ($severity) $msg"
}
puts "logger thread created: [thread::id]"
thread::wait
}]
for {set i 0} {$i < 3} {incr i} {
make_worker_thread $logger {
proc post_msg {} {
log notice "A message from [thread::id]"
after 1000 ::post_msg
}
puts "worker thread created: [thread::id]"
after 1000 ::post_msg
thread::wait
}
}
vwait forever
Этот код создает один поток регистратора и четыре рабочих потока, каждый из которых отправляет сообщение потоку регистратора один раз в секунду. Код выполняется до тех пор, пока не будет прерван вручную. Поток регистратора просто бесхитростно выводит сообщение, которое было передано на консоль, но, как уже упоминал кто-то еще в этом потоке, вы, вероятно, могли бы использовать пакет «logger» из Tcllib, если вам нужны такие необычные вещи, как средства.
Чтобы повторить мои доводы:
- Сам пакет logger, по-видимому, ничего не знает о потоковой передаче.
- Потоки Tcl хорошо разделены и обычно взаимодействуют посредством передачи сообщений.
- Следовательно, создайте поток для регистратора и научите рабочие потоки отправлять ему сообщения; следовательно, рабочие потоки не связаны с тем, как реализован регистратор.
P.S. В рабочих потоках вы можете использовать [thread::send -async ...]
, чтобы сделать отправку сообщений журнала полностью асинхронной.
Комментарии:
1. Спасибо. Похоже, это решение. Знаете ли вы какие-либо существующие и надежные реализации такого выделенного потока ведения журнала?
2. @Andrey, зачем тебе это нужно? Кажется, это несложно: по сути, вы создаете новый поток, а затем загружаете в него этот пакет «logger», загружаете его и затем входите в цикл событий. Другие потоки используют
thread::send
идентификатор этого потока. Начните с wiki.tcl.tk/Thread3. Я хотел бы интегрировать пакет logger с многопоточностью. И я не хочу исследовать внутренности регистратора, если кто-то другой уже выполнил эту работу.
4. @Andrey, кажется, вы не поняли мою идею. Я предложил не интегрировать пакет logger с многопоточностью, а скорее использовать способ Tcl для выполнения многопоточности, чтобы заставить logger вообще не беспокоиться о многопоточности. Я обновил свой пост рабочей программой, демонстрирующей концепции.
Ответ №2:
Эта реализация потокобезопасна. Из-за общего назначения C-функциям не требуется tcl-интерпретатор.
Комментарии:
1. Спасибо за ответ. Где я могу найти ссылки на загружаемый архив и документацию? Должен ли я связаться с разработчиком по его электронной почте?
2. @bilash.saha, это для пакета «logger» из Tcllib. На самом деле я спрашивал об этом посте tcl-core 😉
Ответ №3:
Это немного зависит от того, чего вы хотите достичь с помощью многопоточного использования logger.
Если у вас просто есть вариант использования, чтобы не блокировать ваши рабочие потоки при записи сообщений журнала на диск, самый простой способ — использовать logger в обычном режиме и настроить простой logproc, который выполняет thread::send -async для некоторого потока ведения журнала (который сам может использовать logger с добавлениями для записи фактических файлов журнала) с вашим сообщением журнала (в основном, то, что было набросано в принятом ответе).
Если вы хотите использовать опцию loggers для отключения / включения ведения журнала для всей программы в разных потоках, вам нужно проделать немного больше работы, чтобы распространить изменения на уровне журнала на все потоки с помощью пользовательских lvlchangeproc.
Ответ №4:
Вот моя «многопоточная» оболочка для logger
пакета:
# replacement for logger::init procedure from logger package
proc ::mylogger::init { service } {
set log [logger::init $service]
foreach lvl [logger::levels] {
interp alias {} log_to_file_$lvl {} ::mylogger::log $lvl $service
${log}::logproc $lvl log_to_file_$lvl
}
return $log
}
proc mylogger::server { } {
set t [thread::create {
proc log { level txt } {
set msg "[[clock format [clock seconds] -format "%Y-%m-%dT%H:%M:%S"]]t$levelt$txt"
puts stderr $msg
}
# enter to event loop
thread::wait
}]
tsv::set measure-logger loggerThread $t
}
proc ::mylogger::log { level service txt } {
set t [tsv::get measure-logger loggerThread]
thread::send -async $t [list log $level "$servicet$txt"]
}
# EXAMPLE
# start logging thread
# should be called once from main application thread
::mylogger::server
# create logger
# may be called from any thread
set log [mylogger::init myservice]
# log a message
# may be called from the thread the "mylogger::init myservice" was called in
${log}::debug myservice "Hello, World!"
# wait a second
after 1000
Комментарии:
1. Вероятно, работает.
tsv::get
Подход в::mylogger:log
выглядит немного сомнительно, я бы просто создал пользовательский процесс со встроенным правильным идентификатором потока вместо того, чтобы вызыватьtsv::get
при каждом вызове logging. Самый простой способ сделать это — в::mylogger::init
процедуре, где вы в любом случае настраиваете псевдонимы ведения журнала, вы могли бы просто добавить идентификатор потока в качестве фиксированного 4-го параметра к псевдониму.