asyncioがPOSIXスレッドを使っている原因を調べる

tokibito先生 (id:nullpobug) がオフィスに遊びにおいでと声かけてくれたので、オープンコレクターさんに遊びに行ってました。 aodag先生 (id:aodag) と3人で雑談してたんですが、ふと以前気になっていたことを思い出したので聞いてみた。

気になっていたこと

とある勉強会の発表資料 を作っている時に、 asyncioとaiohttpを使ってとあるサーバにHTTPのリクエストを送るコード例を用意した。

import aiohttp
import asyncio

async def fetch(l, url):
    async with aiohttp.ClientSession(loop=l) as session:
        async with session.get(url) as response:
            return await response.text()


async def main(l, url, num):
    tasks = [fetch(l, url) for _ in range(num)]
    return await asyncio.gather(*tasks)


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    results = loop.run_until_complete(main(loop, 'http://localhost:8000', 3))
    for r in results:
        print(r)

PyCharmでは、「Concurrency Diagram」を表示する機能があり、スレッドやプロセスの動きを確認できるのですが、 このコードを実行したときのスレッドの動きは次のようになる。

f:id:nwpct1:20170330234242p:plain

なぜか、 concurrent.futures.ThreadPoolExecutor が現れている。 ドキュメントに何か書いてあるのか調べてみたのですが、それらしい記述が見つからず諦めていたのでaodag先生とtokibito先生に聞いてみた。

socket.getaddrinfo

数十分で原因を見つけてくれた。 socket.getaddrinfo が同期的に実行されてしまうため、cpythonの実装としてはこれを非同期に実行できるように変えるのではなく、ひとまず concurrent.futures.ThreadPoolExecutor により複数のスレッドで実行するようにしているらしい。

試しにgetaddrinfoが使われないコードサンプルとして、 aioredisを使ったサンプルを用意した。 ローカルに建てたRedisのサーバにUNIXドメインソケットで繋いでみる。

import asyncio
import aioredis

async def connection_example(key):
    conn = await aioredis.create_connection('/tmp/redis.sock')
    return await conn.execute('GET', key)


async def main(num):
    tasks = [connection_example('my-key') for _ in range(num)]
    return await asyncio.gather(*tasks)


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    results = loop.run_until_complete(main(3))
    for r in results:
        print(r)

ちなみにredisのconfigは↓。

daemonize no
pidfile /var/run/redis.pid
unixsocket /tmp/redis.sock
unixsocketperm 700
logfile ""
databases 1

この時のConcurrency Diagramを見ると、

f:id:nwpct1:20170331163806p:plain

たしかにスレッドが生成されていない。

外部のRedisサーバへのアクセス

一方でRedisのサーバを外部に用意して繋いでみると (今回は arukas.io を使わせていただきました)、

import asyncio
import aioredis

async def connection_example(key):
    conn = await aioredis.create_connection(
        ('seaof-xxx-xxx.arukascloud.io', 311390),
        db=0, password='xxxxxxxxxxxxxxx')
    return await conn.execute('GET', key)


async def main(num):
    tasks = [connection_example('my-key') for _ in range(num)]
    return await asyncio.gather(*tasks)


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    results = loop.run_until_complete(main(3))
    for r in results:
        print(r)

実行すると次の通り。

f:id:nwpct1:20170331163620p:plain

おー やっぱりワーカースレッドが生成されてしまうらしい。

ThreadPoolExecutorのワーカースレッドはいくつまで生成されるのか。

Semaphoreで同時に実行される数を3つに制限した時のConcurrency Diagramは次のようになる。

f:id:nwpct1:20170331164208p:plain

どうやらThreadPoolExecutor内のThreadが再利用されていない。 どこまで生成されるのかは、ドキュメントのThreadPoolExecutorのところに書いてあった。

max_workers が None か指定されない場合のデフォルト値はマシンのプロセッサの数に 5 を掛けたものになります

17.4. concurrent.futures – 並列タスク実行 — Python 3.6.1 ドキュメント

試しに30個ぐらいリクエストを送ってみる(Semaphoreは3)。

f:id:nwpct1:20170331164432p:plain

20個まで生成され、それ以降は再利用されているのを確認できた。 実装上ワーカースレッドの上限は変更できないけど、たしかに別に困るケースもなさそう。

uvloop

uvloopの方は、POSIXスレッド使わずに頑張ってるかもという話が出たので確認。

import uvloop

# 中略
loop = uvloop.new_event_loop()
asyncio.set_event_loop(loop)

実行すると、

f:id:nwpct1:20170330232815p:plain

おー ほんとだ。

おわりに

aodag先生とtokibito先生すごい… 自分はドキュメントを読みつつモヤモヤしたまま放置してたのですが、数十分で原因を見つけて教えてくれた。 自分もいよいよ明日から社会人なので、お二人目指して精進します。

aodag先生とtokibito先生のいるオープンコレクターさん、お仕事募集中だそうです(お世話になったので一言宣伝)。 ありがとうございました!

2017年抱負(エンジニアリング)

今週のお題「2017年にやりたいこと」

あけましておめでとうございます。 今年やりたいことを書き出してみる。

nwpct1.hatenablog.com

2016年はインプットのかなり多い年だったので、今年はアウトプットを増やしたい。

  • 卒論の提出と卒業: まずはちゃんと卒業します。
  • 書籍の出版: 実はもう動いているので必ずやりきる。これは2016年の目標でもありましたが、告知できるのは今年の5月頃になりそうです。
  • Webサービスをリリース: いま作りたいものがあるのでなんとか形にしたい。
  • Github 100 stars: 2016年の目標だったんですが、最も集めたのはKobinの50starsでした。今年こそ。

うーん... 直近でやりたいこと・やらないといけないことが多くてあんまり思いつかなかった。 もうちょっと余裕を持った生活が一番必要かもしれない。

抱負とまではなっていないんですが、今気になってる技術は、

  • PostgreSQLドライバ実装: 以前、nakagamiさんにPostgreSQLのMessage Formatプロトコル を丁寧に解説してもらったので、一度書いておきたい
  • Rustでシステムコールを直接バンバン叩く何かつくってみたい: 作るもの思いつかなければ、HTTPのサーバとかいいかな...
  • 非同期のPythonサーバの実装: まだServerとのInterfaceがPEPに無いので、何らかの形で関われると嬉しい

おわりに

今年もよろしくお願いします。

2016年のふりかえり

2016年も終わりですね。大学院には進まずに都内のWeb系企業に就職するので、学生生活もそろそろ終わりみたいです。 3月頃から渋谷新宿あたりにいると思うので、またご飯行きましょう🍺

OSS活動

f:id:nwpct1:20161231143547p:plain

Write Code Every Dayには程遠いですが、昨年に比べてコードを書く量も増えてきています。 さすがに技術的にも成長はしているみたいで、コーディングスピード(特にPython)があがったのもContributionsの数に繋がったのかな。 つくってきたOSSをふりかえる。

Kobin

趣味でWebフレームワークとドキュメンテーション、サンプルアプリ、周りのツール群を全部1人で書いていくのは思っていたよりも多くの時間が必要ですが、学んだことは思っていた以上にかなり多かったです。学生のうちに取り組んでおいてよかったことの1つですね。

github.com

まだまだ利用者が多いわけではないので、新しい構文やモジュールは気軽に導入していて、Pythonのバージョンアップで追加されたモジュールや構文を試す意欲が湧いていい。

来月の神戸Pythonの回で、Kobinのハンズオンをすることになったので、近くの方は是非参加して下さい。 自分の作ったフレームワークのハンズオンの依頼を受けるのはうれしいですね。

その他

Kobin関連以外だと、公開したソフトウェアはこのあたり。GoやPython、ElectronアプリケーションにAngular2のUIコンポーネント

ソフトウェア以外だと、PyCon JPやPyCon APAC/Koreaでの登壇やgihyo.jpでの連載ですね。 今年も英語の勉強がついつい後回しになっていてまずい。

あとはポートフォリオもAngular2で更新した。

http://c-bata.link

デザイン

自分でもロゴやWebデザインがある程度出来るといいなと思っていて、IllustratorとかSketchでロゴを書いたりしている。 悩んだときは id:denari01 に頼るようにしていて、相談すると自分では言葉にできない違和感をちゃんと日本語に落とし込んで解説してくれる。

Kobinのロゴいいよねって言ってくださる方が結構いらっしゃるんですが、これは彼が考えてくれたものです。そろそろなにか美味しいもの奢らないといけない。

Repository Design
Feedy Feedy
Comet comet
kobin-todo kobin-todo

写真

余談ですが、デザインの延長で写真に興味が湧いてきていて、旅行のときはSONYのα6000を持ち歩いてる。

高専の卒業と就職

7年間通った高専・専攻科も (卒研が無事に終われば) 今年度で卒業みたいです。長かったですね。

情報系の技術レベルに関して疑問に思うことは何度かありましたが、専攻科のクラスメートは信じられないくらい優秀で、他の高専でも id:puhitaku みたいにものづくりに対して半端ない熱量持ってる人がいるのでいい刺激になりました。 5年後・10年後には、みんなかなり活躍してる気がするので、自分もそのときには恥ずかしくないくらいには活躍したい。

卒研終わったらクラスメートとあと1回くらいは海外行こう。

2017年

技術的な興味を優先してきたのですが、ある程度形になってきたところで満足して、中途半端な完成度のまま放置しているものも多かったです。 最近は、Sphinxのメンテナをされてる方やJasper(Electron製のGithub Issues Reader)の開発をされている方のブログ記事を読んで、どういうものづくりしていきたいのかとか、モチベーションの保ち方について考えることがありました。

ちゃんと作り込むところまでモチベーションを保つには「自分が楽をできる」とか「お金になる」みたいな分かりやすい理由がもう少し必要そうです。 今は1つ実現したいアイデアがあるので、とりあえず .io ドメインを購入してKobinを使って実装しています。 4月までにはある程度形にしてプライベートベータで知り合いには試してもらえるようにしたい。

おわりに

GithubのメールアドレスやWantedlyへのスカウトメール・メッセージが、2015年ぐらいからかなり増えてきていて、知名度もあがってきたのかなと嬉しく思ってました。 うまくいかないことも結構ありましたが、こうやって文字に起こしてみるとそれなりに色々やってきたみたいです。

2017年もよろしくお願いします。