Потокобезопасный регистратор для Tcl

#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/Thread

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

4. @Andrey, кажется, вы не поняли мою идею. Я предложил не интегрировать пакет logger с многопоточностью, а скорее использовать способ Tcl для выполнения многопоточности, чтобы заставить logger вообще не беспокоиться о многопоточности. Я обновил свой пост рабочей программой, демонстрирующей концепции.

Ответ №2:

API ведения журнала для Tcl

Эта реализация потокобезопасна. Из-за общего назначения 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-го параметра к псевдониму.