「プロセスの優先度を変更する」のではなくて「サブプロセスを起こす際に優先度を指定したい」話。
「Unix vs Windows」という文脈で語る際、Windows に対して「nice (/renice)がないでやんの、ヴァーカヴァーカ」とは言えなくて、実際は「CreateProcess に直接優先度を指定できる」という Unix なプロセス API にはない特徴があり、実はこれについては微妙に引き分けだったりする。(Unix 系の場合は、システムコールレベルでのサポートではなくて、nice コマンド経由でプロセスを実行出来るようになっているので、エンドユーザから見た場合は違いはないとも言える。)
Python は、よほどのことがない限りは、ほとんどのものが「Unix での考え方の方に従う」ように作らていて、ゆえに、subprocess モジュールが(Windows で出来るからという理由で)「プロセス生成時に優先度を指定出来る」ようには作られてなくて、これは psutil も同じである。
この事実がまぁ悩ましくてな。「世界は Unix で出来ている」ならば、これでいいってわけなのよな:
1 # -*- coding: utf-8 -*-
2 import subprocess
3
4 # ...
5 subprocess.check_call(["nice", "-20", "my_command", "arg"])
もちろん Windows には nice コマンドがないので、こんなことは出来ない。CreateProcess で指定できるのになんでだよ、てことになってしまう。
python が nice の存在を暗に仮定している、あるいは Windows の CreateProcess で優先度指定が出来ることを黙殺している、という事実は、優先度指定をプロセス生成時に行いたいケースにおいては「subprocess モジュールが提供する安直ヘルパ関数(とか)の全滅」を意味していて、そうしたい場合、「subprocess.Popen」にまで遡る必要がある。けどまぁ出来る:
1 from __future__ import unicode_literals
2
3 import sys
4 import subprocess
5
6
7 if hasattr("", "decode"):
8 _encode = lambda s: s.encode(sys.getfilesystemencoding())
9 else:
10 _encode = lambda s: s
11
12
13 def _filter_args(*cmd):
14 """
15 do filtering None, and do encoding items to bytes
16 (in Python 2).
17 """
18 return list(map(_encode, filter(None, *cmd)))
19
20
21 def check_call(*popenargs, **kwargs):
22 """
23 Basically do simply forward args to subprocess#check_call, but this
24 does two things:
25 * It does encoding these to bytes in Python 2.
26 * It does omitting `None` in *cmd.
27
28 """
29 import psutil
30 def _call(*popenargs, timeout=None, **kwargs):
31 with psutil.Popen(*popenargs, **kwargs) as p:
32 try:
33 p.nice(psutil.IDLE_PRIORITY_CLASS)
34 return p.wait(timeout=timeout)
35 except: # Including KeyboardInterrupt, wait handled that.
36 p.kill()
37 # We don't call p.wait() again as p.__exit__ does that for us.
38 raise
39 def _check_call(*popenargs, **kwargs):
40 retcode = _call(*popenargs, **kwargs)
41 if retcode:
42 cmd = kwargs.get("args")
43 if cmd is None:
44 cmd = popenargs[0]
45 raise subprocess.CalledProcessError(retcode, cmd)
46 return 0
47 cmd = kwargs.get("args")
48 if cmd is None:
49 cmd = popenargs[0]
50 _check_call(_filter_args(cmd), **kwargs)
ローカル関数の「_call」と「_check_call」が subprocess モジュールからコピーしてきたもので、前者内を「psutil.Process」に置き換えた上で即座に「.nice(…)」を呼び出している。
正直 psutil が「プロセス生成時に指定できる」ように考えてくれるのが一番ハッピーな気がするんだけれど、まぁ…「安直ヘルパに依存しなければいいだけ」と言っちゃえばそれだけとも言えるので、歯痒いねぇ…。
まぁ psutil なしの場合はさらに状況はヒドいわけだから、やっぱり結局は「psutilさn、ありがとぅーありがとぅー」ではある、と。うーむ。