async 20170804.2158(in MELPA)
Asynchronous processing in Emacs

概要

async.el はEmacs Lispで 並列処理 を行うライブラリです。

作者はEmacsの世界的権威 John Wiegley 氏。

eshellやuse-package.elbind-key.el など
多くのEmacs Lispをリリースしています。

Emacs Lispでは並列処理が弱点と言われていますし、
できないと思っている人もいるようですが、
実はある方法を使えば可能なのです…

確かにEmacs Lispという言語自体では
並列処理の機能はないのですが、
プロセス を使うという抜け道があります。

M-x shellでシェルを動かしながら
Emacsを動作させているのと同じです。

async.elは新しいEmacsプロセスでS式を評価したり、
S式を返す外部プログラムを起動したりすることで
Emacs Lispで並列実行を行います。

deferred.el でも並列処理をしますが、
あれはタイマーによる疑似スレッドなので
残念ながらせっかくのマルチコアが生きません。

インストール

パッケージシステムを初めて使う人は
以下の設定を ~/.emacs.d/init.el の
先頭に加えてください。

(package-initialize)
(setq package-archives
      '(("gnu" . "http://elpa.gnu.org/packages/")
        ("melpa" . "http://melpa.org/packages/")
        ("org" . "http://orgmode.org/elpa/")))

初めてasyncを使う方は
以下のコマンドを実行します。

M-x package-install async

アップグレードする方は、
以下のコマンドでアップグレードしてください。
そのためにはpackage-utilsパッケージが必要です。

M-x package-install package-utils (初めてアップグレードする場合のみ)
M-x package-utils-upgrade-by-name async

async-start

async-start 関数は新しいEmacsプロセスで評価するラムダ式と、
実行終了時に評価する関数の2つを取り、並列実行をします。

新しいEmacsプロセスは
emacs -Q -batch により
まっさらな環境で評価します。

そのため、現在の変数の値をそのまま引き継ぐことはできません。

そこで async-inject-variables で新しいEmacsに
持ち込む変数を正規表現で指定します。

返り値は、それらの変数を設定するsetqのS式です。

たとえば、a=3を持ち込む場合は

(async-inject-variables "^a$")
↓
(setq a (quote 3))

となります。

これを使う場合は バッククォート でラムダ式を作ります。

変数を参照するには レキシカルスコープ が必要なので
lexical-letlexical-binding を使う必要があります。

;;; -*- lexical-binding: t -*-
(require 'async)
;;; tmを後のlambdaで参照するためlexical-bindingが必要
(let ((tm (float-time)))
  (async-start
   ;; What to do in the child process
   (lambda ()
     (message "This is a test")
     (sleep-for 3)
     222)

   ;; What to do when it finishes
   (lambda (result)
     (message "Async process done, result should be 222: %s (%s sec)"
              result (- (float-time) tm)))))
;;; 誤例: nil  新プロセスでの (buffer-file-name) を評価してしまう
(async-start
 (lambda ()
   (buffer-file-name))
 (lambda (result)
   (message "buffer-file-name: %s" result)))
;;; バッククォートにより結果を渡すことでカレントバッファのbuffer-file-nameが返る
(async-start
 `(lambda ()
    ,(buffer-file-name))
 (lambda (result)
   (message "buffer-file-name: %s" result)))
;;; 変数custom-fileをプロセスに渡す→現在のcustom-fileが返る
(async-start
 `(lambda ()
   ,(async-inject-variables "^custom-file$"))
 (lambda (result)
   (message "buffer-file-name: %s" custom-file)))

async-start-process

async-start-process 関数は、任意のプロセスを立ち上げ、
その結果を コールバック関数 に渡します。

とくにプロセス名(第1引数)がemacsのとき、
コールバックに渡される引数は
そのプロセスの出力結果のS式です。

これはなかなか興味深い機能で、S式を出力する外部プログラムを作成すれば、
Emacs Lispで書くと遅い処理でもEmacsが止まることなく処理してくれるでしょう。

たとえばアイドル時にカレントバッファのソースコードを解析して、
その結果を変数に保持するようなこととかです。

夢を見させてくれる機能ですが、やはりネックは
現在の状態をいかにプロセスに渡すかでしょう。

この例では簡単のためににechoでS式を出力させているだけです。

;;; 単にプロセスが返る
(async-start-process "hoge" "echo" (lambda (x) (message "ret:%S" x)) "(+ 1 2)")
;;; プロセス名にemacsを指定すると、出力をS式とみなしてくれる→(+ 1 2)が返る
(async-start-process "emacs" "echo" (lambda (x) (message "ret:%S" x)) "(+ 1 2)")
;;; evalすると3が返る
(async-start-process "emacs" "echo" (lambda (x) (message "ret:%S" (eval x))) "(+ 1 2)")

本サイト内の関連パッケージ


本日もお読みいただき、ありがとうございました。参考になれば嬉しいです。