19 октября 2011

Удивительное рядом: потокобезопасность в cl

Обычная задача: выполнить функцию в отдельном потоке, в моем случае это inc-counter.

Для этого можно выполнить форму:

* (defparameter *counter* 1)
* (defun inc-counter ()
    (incf *counter*))
* (bordeaux-threads:make-thread (lambda () (inc-counter)))
* *counter*
2

Усложненная задача: потоков может быть несколько. Определенно функция может изменять какие-то глобальные переменные (в терминах лиспа: динамические переменные созданные функциями defvar, defparameter). Если бы передо мной была библиотека на c/c++, можно было бы опускать руки, так как с/с++ не позволяет изменять окружение выполнения функции. Думаю, что и java так же этого не позволяет. А javascript, кстати, позволяет окружение менять. А вот в common lispе для того, чтобы сделать из любой функции с побочными эфектами, функцию без них, достаточно использовать форму let. Давайте выполним теперь такой поток:

* (bordeaux-threads:make-thread (lambda () (let ((*counter* 0)) (inc-counter))))
* *counter*
2

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

4 комментария:

  1. Да жеж всего лишь динамические переменные.
    А как вытащить из скомпилированной ф-ии имена динамических переменных что она использует, с помощью cltl2, чёрной или другой магии, случайно не знаете?

    ОтветитьУдалить
  2. На ты:)
    Таки да, но эффект удобства понять можно, случайно вспомнив c++.
    Пока не знаю. Хотел было узнать, но отложил, воспользовавшись грепом.

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

    ОтветитьУдалить
  4. Из скомпилированной функции - никак. Т. к. это - динамическое связывание, оно существует только в рантайме. Но можно вытащить во время выполнения кода - прервав нить с помощью bt:interrupt-thread - эта функция фактически прерывает нормальное выполнение нити и выполняет в текущем её динамическом окружении твою функцию - соответственно, из неё будет доступны текущие привязки динамических переменных. Примерно вот так можно сделать:

    CL-USER> (defparameter *counter* 1)
    *COUNTER*
    CL-USER> (defun counter-loop ()
    (loop (incf *counter*)
    (sleep 1)))
    COUNTER-LOOP
    CL-USER> (defparameter *thread* (bt:make-thread (lambda ()
    (let ((*counter* 0))
    (counter-loop)))))
    *THREAD*
    CL-USER> (let (res)
    (bt:interrupt-thread *thread* (lambda ()
    (setf res *counter*)))
    (loop until res)
    res)
    136

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

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