- 投稿日:2020-01-14T08:51:49+09:00
Goでワーカープールを15分で実装する方法
こちらの記事は、Joseph Livni氏により2018年10月に公開された『 Write a Go Worker Pool in 15 minutes 』の和訳です。
本記事は原著者から許可を得た上で記事を公開しています。
私は多くのユーザーリクエストを高速に処理するGoのサービスを構築していました。Goroutineのプールを使ってカプセル化することにより、パフォーマンスは20倍になりました。本記事では独自のワーカープールを作る方法を説明します。1
最終的な結果を知りたい場合は こちら からダウンロードしてください。2
本記事は以下の順番で説明します。
- モックとなるジョブの作成
- ワーカープールの作成
- ベンチマークテストの実施
ファイル構造は以下です。
/go_worker_pool /work work.go /pool worker.go dispatcher.go bench_test.go main.go上記を構築するディレクトリパスは以下のとおりです。
go/src/github.com/Lebonesco/go_worker_pool最終的には以下のようになります。
$ go run main.go 2018/10/06 15:53:43 starting application... 2018/10/06 15:53:43 starting worker: 1 2018/10/06 15:53:43 starting worker: 2 2018/10/06 15:53:43 starting worker: 3 2018/10/06 15:53:43 starting worker: 4 2018/10/06 15:53:43 starting worker: 5 2018/10/06 15:53:43 creating jobs... worker [2] - created hash [2376065843] from word [iCMRAjWw] worker [4] - created hash [121297580] from word [xhxKQFDa] worker [1] - created hash [3193224551] from word [XVlBzgba] worker [3] - created hash [1481401259] from word [hTHctcuA] worker [5] - created hash [166906897] from word [FpLSjFbc] worker [5] - created hash [1752784812] from word [QYhYzRyW] ...モックとなるジョブの作成
完了するまでに時間がかかる処理をシミュレートするために、ランダムな文字列を使って、たくさんのジョブを作成します。ジョブは何らかの処理を実行します。今回は文字列のハッシュを生成します。
work.gopackage job import ( "fmt" "hash/fnv" "time" "math/rand" "os" ) var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") // create random string func RandStringRunes(n int) string { b := make([]rune, n) for i := range b { b[i] = letterRunes[rand.Intn(len(letterRunes))] } return string(b) } // create list of jobs func CreateJobs(amount int) []string { var jobs []string for i := 0; i < amount; i++ { jobs = append(jobs, RandStringRunes(8)) } return jobs } // mimics any type of job that can be run concurrently func DoWork(word string, id int) { h := fnv.New32a() h.Write([]byte(word)) time.Sleep(time.Second) if os.Getenv("DEBUG") == "true" { fmt.Printf("worker [%d] - created hash [%d] from word [%s]\n", id, h.Sum32(), word) } }https://gist.github.com/Lebonesco/3d9758a2c248b004ea4c584796d53c75
DoWork() は string と worker id を受け取って、文字列のハッシュを計算します。結果を出力する前に1秒間sleepします。ジョブの終了時に結果を表示するには、以下をセットします。
$ export DEBUG="true"後ほどベンチマークテストをするときには、ハッシュの結果を表示することは不要であることと、テスト結果が見にくくなることから、制御できるようにしています。
ワーカープールの作成
次は、実際にワーカープールを作成していきましょう。ワーカープールは3つの要素で構成されます。dispatcher (ワーカーをインスタンス化し、ワーカーとワーカープールを接続します)と workers (処理を待っているジョブを受け取り、処理をします)と collector (ジョブの到着を待って、workers に割り当てます)です。
以下のコードで注意すべき重要な内容は WorkerChannel と Worker構造体の Start() メソッドです。
Goでよく知られているフレーズに “Do not communicate by sharing memory; instead, share memory by communicating.” 3 があります。これが WorkerChannel の目的であり、ワーカーとワーカープールを通信する方法です。WorkerChannel は利用できるすべてのワーカーのチャネルを保持します。ジョブが到着すると collector は WorkerChannel からワーカーチャネルを取得し、ジョブをそのチャネルに渡します。ワーカーがそのジョブを受け取ります。逆に、ワーカーは処理が終了するとそのチャネルを WorkerChannel に戻し、次のジョブを待ちます。
worker.gopackage pool import ( "log" work "github.com/Lebonesco/go_worker_pool/work" ) type Work struct { ID int Job string } type Worker struct { ID int WorkerChannel chan chan Work // used to communicate between dispatcher and workers Channel chan Work End chan bool } // start worker func (w *Worker) Start() { go func() { for { w.WorkerChannel <-w.Channel // when the worker is available place channel in queue select { case job := <-w.Channel: // worker has received job work.DoWork(job.Job, w.ID) // do work case <-w.End: return } } }() } // end worker func (w *Worker) Stop() { log.Printf("worker [%d] is stopping", w.ID) w.End <- true }https://gist.github.com/Lebonesco/3fc3bb1dde81bf6ac676640ea0e5abd0
すばらしい!終わりに近づいています。ワーカープールを仕上げるために dispatcher と collector を完成させましょう。その前に、要素を組み立てるという点では、ワーカープールを設計する方法はたくさんあることを知っておいてください。
コードの可読性や実装方法に応じて、よりうまくいく方法があります。workers, dispatcher, collector の要素を用いて、あなたのプロジェクトに応用できるでしょう。たとえば collector を dispatcher から分ける人もいます。私の場合 dispatcher から Collector 構造体を返します。独自のワーカープールを実装したい人にとって可読性が良いと考えているためです。4
dispatcher.gopackage pool import ( "log" ) var WorkerChannel = make(chan chan Work) type Collector struct { Work chan Work // receives jobs to send to workers End chan bool // when receives bool stops workers } func StartDispatcher(workerCount int) Collector { var i int var workers []Worker input := make(chan Work) // channel to recieve work end := make(chan bool) // channel to spin down workers collector := Collector{Work: input, End: end} for i < workerCount { i++ log.Println("starting worker: ", i) worker := Worker{ ID: i, Channel: make(chan Work), WorkerChannel: WorkerChannel, End: make(chan bool)} worker.Start() workers = append(workers, worker) // stores worker } // start collector go func() { for { select { case <-end: for _, w := range workers { w.Stop() // stop worker } return case work := <-input: worker := <-WorkerChannel // wait for available channel worker <-work // dispatch work to worker } } }() return collector }https://gist.github.com/Lebonesco/7e696ca0a7bd487cc909b10a1385cfcd
最後に、アプリケーションを動かすドライバーを作りましょう。main() 関数がワーカープールをインスタンス化し、ジョブを作成します。
main.gopackage main import ( "log" "github.com/Lebonesco/go_worker_pool/pool" work "github.com/Lebonesco/go_worker_pool/work" ) const WORKER_COUNT = 5 const JOB_COUNT = 100 func main() { log.Println("starting application...") collector := pool.StartDispatcher(WORKER_COUNT) // start up worker pool for i, job := range work.CreateJobs(JOB_COUNT) { collector.Work <-pool.Work{Job: job, ID: i} } }https://gist.github.com/Lebonesco/3f725e96bdc86f71742e0b5929aa6dde
ベンチマークテストの実施
このワーカープールが実際に機能することを確認するために、簡単なベンチマークテストを動かしてみましょう。Goはテストフレームワークが組み込まれているため、とても簡単にできます。
bench_test.gopackage main import ( "testing" "github.com/Lebonesco/go_worker_pool/pool" work "github.com/Lebonesco/go_worker_pool/work" ) func BenchmarkConcurrent(b *testing.B) { collector := pool.StartDispatcher(WORKER_COUNT) // start up worker pool for n := 0; n < b.N; n++ { for i, job := range work.CreateJobs(20) { collector.Work <-pool.Work{Job: job, ID: i} } } } func BenchmarkNonconcurrent(b *testing.B) { for n := 0; n < b.N; n++ { for _, job := range work.CreateJobs(20) { work.DoWork(job, 1) } } }https://gist.github.com/Lebonesco/9e4048e2ef7438af8fda81874850e6f7
それでは動かします。
$ go test -bench=. starting worker: 1 starting worker: 2 starting worker: 3 starting worker: 4 starting worker: 5 goos: windows goarch: amd64 pkg: tutorials/concurrent-limiter BenchmarkConcurrent-4 1 3001744600 ns/op BenchmarkNonConcurrent-4 1 20006911100 ns/op PASS ok tutorials/concurrent-limiter 23.291s結果はこのとおりです。ワーカープールによりパフォーマンスが大幅に良くなります。さらに、個々のジョブの所要時間が長くなり、動いているワーカー数が増えるほど、パフォーマンスがより良くなることがわかるでしょう。
私の記事を読んでくれてありがとう。
この記事がお役にたちましたら、教えてください???
さらに読みたい場合は、下の「フォロー」ボタンをクリックしてください。
訳注: ライブラリとして提供されているワーカープールとしては gammazero/workerpool などがあります ↩
訳注: done channelを用いてgoroutineを終了するようになっていますが、Go1.7からcontextが導入されたのでcontext.Done()を用いることもできます ↩
訳注: その他の応用として、例えば Collector にバッファ付きチャネルを加えて、キューイングする処理を追加することもできます ↩
- 投稿日:2020-01-14T07:22:40+09:00
自作SORACOM InventoryのエージェントinventorydでIoTデバイスを簡単に遠隔操作する
はじめに
IoTデバイスの遠隔操作したくないですか?
したいに決まってますよね!
ということで今回お勧めするのはSORACOM Inventoryです。1分でラズパイを遠隔再起動するよ
注意
すでにSORACOM Inventoryを使用中の場合、以下を実行すると費用が発生します。(初期費用100円、利用料金50円)
未使用の場合は150円の無料枠に入ります。
SORACOM Airで接続中のラズパイに入ってrootで以下のコマンドを実行しましょう。
curl -L -O https://github.com/1stship/inventoryd/releases/download/v0.0.1/inventoryd_0.0.1_linux_arm.tar.gz tar zxf inventoryd_0.0.1_linux_arm.tar.gz ./inventoryd --init # 質問にはEnterのみでよい(yで回答したことになる) echo "/sbin/reboot" > resources/3/0/4 ./inventoryd -bこんな感じで表示されればOKです。
2020/01/13 12:37:59 Start Bootstrap 2020/01/13 12:38:00 Request Bootstrap accepted 2020/01/13 12:38:01 Bootstrap finished Bootstrap finish 2020/01/13 12:38:01 Registering... 2020/01/13 12:38:04 Register finished. Location is SKdsWwKWEq 2020/01/13 12:38:04 READ /1/0 2020/01/13 12:38:04 READ /2/0 2020/01/13 12:38:05 READ /3/0 2020/01/13 12:38:05 READ /4/0 2020/01/13 12:38:06 READ /5/0 2020/01/13 12:38:06 READ /6/0 2020/01/13 12:38:06 READ /7/0 2020/01/13 12:38:07 READ /8/0 2020/01/13 12:38:07 READ /9/0SORACOMコンソールにログインして、SORACOM Inventoryのデバイス管理メニューに入ります。
デバイスを選択して、詳細をクリックします。
Reboot /3/0/4を探して、実行ボタンをクリックします。
「コマンド実行」をクリックします。
2020/01/13 12:38:58 EXECUTE /3/0/4 Connection to 192.168.2.3 closed by remote host. Connection to 192.168.2.3 closed.はい、ラズパイが再起動しました。やったね!完!
SORACOM Inventoryとは
完!でもよかったのですが、上でやったことは何なのか説明をします。
皆さん、SORACOM Inventory使ってますか?というかそもそも知ってますか?
ぶっちゃけ使ってない人多いんじゃないかと思います。Qiitaのキーワード0件でしたし。(記事自体はありました)SORACOM Inventoryの公式の説明は以下の通り。
Inventory は、OMA LightweightM2M(LwM2M)をベースにしたデバイス管理のためのフレームワークを提供するサービスです。SORACOM Air と連携したデバイスの自動登録が可能です。
LwM2Mというプロトコルを使ってデバイスを管理するためのサービスです。LwM2Mで必要になるサーバはSORACOMにてフルマネージドで提供されています。そのため、自前でサーバやAWSなどのクラウドを用意することなく、デバイス管理や遠隔操作ができるようになります。
「I」のサービスである「Inventory」は、「H」のサービスである「Harvest」の次に出たサービスです。Harvest以前のサービスのBeamやFunnelは他のサービスと連携することが前提になっていましたが、他のクラウドサービスが必要なくSORACOMだけで完結できるHarvestはとっつきやすく、IoTの入り口として最適ですよね。そしてHarvestによってデータの蓄積や可視化(これはInventoryの翌年出たLagoonで強化される)ができるようになってくると、それをもとにデバイスに制御をかけたい、というのは当然の流れ、、そしてデバイス管理サービスのInventoryが登場します。これもSORACOMだけで完結できる、という点はHarvestと同じで、ユーザーの用意するものはデバイスとSORACOM Airの回線のみです。
使ってみると、デバイスのステータスを取得したり再起動したりということが、SORACOMのコンソールやAPI経由で簡単にできるようになるので、デバイス管理や遠隔操作にはとっても便利です。
でもちょっと大変なところがありまして、LwM2Mのエージェントを自分で作らなければならないんですよね。そのため導入のハードルが高いと思われがちです。(SORACOM HarvestのとりあえずTCP/UDPでデータ投げときゃOKからの落差がすごい)
そもそもLwM2Mって何?というところから始まって、WakaamaやLeshanなどの参照実装や、懇切丁寧なREADMEのあるSORACOM Inventory agent for Javaはあるものの、これを読んで自分のデバイスに合わせてソースを改変してビルドするのはまあ大変です。なんか簡単に扱えるようにしたい。
SORACOM Inventoryのエージェントを自作しよう
というところで色々あって僕はSORACOM Inventoryには思い入れがあるので、なんかやろうかなと思いまして、扱いやすいSORACOM Inventoryのエージェントを自作することにしました。というか2019年のゴールデンウイークを丸ごと費やして作って、inventorydという名前でGitHubに公開していたのですが、当時はブログを書いたりしていなかったのでここで改めて紹介します。
inventoryd
SORACOM Inventory access tool in Golang
https://github.com/1stship/inventoryd基本的な考えは以下の2つです。
・ビルド済のシングルバイナリで動作する
・リソース対応のためにリビルドが必要なく、ファイルの配置で対応できる要はApache HTTP Server(httpd)と同じ感じで使えるようにしたい、ということです。名前もそれっぽくしています。
多くの人はWebサーバ(HTTPサーバ)を立てようとした時、HTTPサーバのソースを改変してビルドしたりしないと思うんですよね。大抵は、httpdやnginxをビルド済のパッケージをインストールして、公開用のディレクトリにファイルを置いたり、PHPなどのスクリプトファイルを置いたりするところから始めると思います。そんな感じで使えるものであれば、ソースコードを読んだり改変したりビルドしたりしない非開発者の人でもSORACOM Inventoryを使えるようになるかもしれない。
そんなものを目指して作りました。Go言語で書いているのはビルド済みのシングルバイナリで提供するのが一番ユーザー側に負担がかからず簡単に導入できるだろう、ということと、当時Go言語に興味があって何か作ってみようという思いがあったからです。(Go言語で書いた最初のプログラムなのでクオリティはお察し。。プロトタイプ的なものと思っていただければ)
inventorydの基本的な考え方
LwM2Mでは、デバイスへの問い合わせや書き込み、実行などをオブジェクトモデルというもので定義し、そのモデルをパスに割り当てています。
例えば先ほどの「Reboot /3/0/4」というのは、Deviceというモデルの仕様にObjectIDが3と定義されており、次の0は最初のリソースを表し、(同じ定義のモデルが複数ある場合は/3/1/4、3/2/4と2個目の数字が上がっていく)、その中のItem ID="4"としてRebootが以下のように記載されています。
<Item ID="4"> <Name>Reboot</Name> <Operations>E</Operations> <MultipleInstances>Single</MultipleInstances> <Mandatory>Mandatory</Mandatory> <Type/> <RangeEnumeration/> <Units/> <Description> <![CDATA[ Reboot the LwM2M Device to restore the Device from unexpected firmware failure. ]]> </Description> </Item>従って、/3/0/4はDeviceというモデルの最初のリソースに対してRebootというExecuteを実行するもの、ということになります。HTTPのRESTのパスであれば、
POST /Device/0/Reboot
のようになっても良さそうなものですが、LwM2Mは通信が軽量なプロトコルを目指しており、上のような仕様がサーバ、デバイス間で共有されているものとして、番号で対象を伝えることになっています。このパスと、デバイス内のファイルを一致させるというのがinventorydの基本的な考えです。
./inventoryd --initを実行した時に、resourcesディレクトリが作成され、その中にはオブジェクトモデルに応じたリソースのファイルが作成されています。そしてこのファイルに対してREADするとそのファイルの中身が返り、WRITEするとファイルが書き換えられ、EXECUTEするとそのファイルがスクリプトとして実行される、という動作をします。
init直後はリソースはデフォルト値を返すだけのものになっており、あまり役に立ちません。このファイルを適切な値やスクリプトで置き換えたり、WRITEされた値を他のプログラムで使用するなどすることによって、ちゃんとしたデバイス管理ができることになります。最初の例では/3/0/4を/sbin/rebootとしましたが、これにより/3/0/4(LwM2Mで定義されたReboot)をラスパイの再起動(/sbin/reboot)と結びつけることができました。
このようなファイルによる対応付けであれば、CやJavaのコードを書いてビルドすることなくファイルの読み書きやシェルスクリプトの記載で対応できるので、比較的簡単に対応できそうですよね。
また、READやWRITEをファイル読み書きでは無く、スクリプトとして実行させたいという場合は、ファイル名の.read、.writeとつけて、実行権をもったスクリプトとすることで対応できるようにしています。
わかりやすいところで説明すると、Current Time /3/0/13はinit直後の状態ではタイムスタンプが0(1970/01/01 00:00:00 UTC)を返すだけですが、/3/0/13.readを
date +%sとして記載し、
chmod 755 resources/3/0/13.readとして実行権を付与することで、現在時刻を返すようになります。
同様に、/3/0/13.writeに時刻を設定するスクリプトを記載することで、時刻をOSに反映させることができます。(値は標準入力で渡すので、readで受け取れます)
read TIMESTAMP date -s "@$TIMESTAMP"これで時刻の設定ができました。(ちなみにタイムスタンプ型はコンソールで読み出すと、「Mon Jan 13 14:14:38 UTC 2020」のような形になるのですが、書き込む時にはYYYYmmddTHHMMSS.fff(UTCからの差、日本時間だと+09)にしないと送信できないという罠があります)
このように、比較的簡単なスクリプトを設置することで、動的な値にも対応できるようになっています。ここではコンソールからの操作をしていますが、当然Web APIも用意されています。
https://dev.soracom.io/jp/docs/api/#/Device
WebAPIを使うことで、管理しているデバイスから値を収集したり、特定の処理を実行させたりを自動化するのも簡単に実現できますね。
ちょっと高度な使い方: Observe
LwM2MにはObserveという面白い仕組みがありまして、サーバから監視対象として設定したリソースは、サーバから読み出さなくてもデバイスから自発的に送信する、というものです。
例えばメモリの使用量を記録するとしましょう。
Memory Freeは/3/0/10なので、/3/0/10.readを以下のスクリプトにします。free | grep 'Mem' | tr -s ' ' | cut -d" " -f7freeコマンドのavailableのメモリを取得しています。LwM2Mの仕様を見ると単位はkBなのですが、変化がわかりにくくなるのでとりあえずbyte単位です。
コンソールから/3/0/10をObserveすると、5秒に1回Notifyというイベントが発生しているのがわかります。
2020/01/13 14:51:37 OBSERVE /3/0/10 2020/01/13 14:51:37 Notify /3/0/10 2020/01/13 14:51:42 Notify /3/0/10 2020/01/13 14:51:47 Notify /3/0/10inventorydではObserveされたリソースの値を5秒に1回確認し、値が変わっていればサーバーに通知する、という動作をします。そしてこのNotifyはSORACOMのアプリケーションサービスのBeam、Funnel、Harvest、Funk、そしてUnified Endpointに送ることができます。
分かりやすいところでHarvestに連携させるとこんな感じです。
メモリ量の推移がharvestに記録されていることがわかりますね。これ結構簡単じゃないですか?やったことはinventorydをダウンロード、初期設定、起動したことと、メモリを取得するスクリプトをちょっとググって入れただけです。それだけでもうこの状態になる。ある意味HarvestにTCP/UDPでデータ送るより簡単ですよ。
また、inventorydはObserveの際、値が変わっていればNotifyという動作をするため、例えば何かのエラーフラグとかを監視させておいて、それがtrueになったらFunkに通知させる、といった使い方もできそうです。
SORACOM Inventory、何だかすごく便利そうじゃないですか?
インターネットからでも使用可能
SORACOM Inventoryの他のアプリケーションサービスにない特長として、インターネットからソラコム回線なしで使用可能、というのがあります。
以下のページの方法でデバイスID、キーを払い出して、
https://dev.soracom.io/jp/start/inventory_registration_with_keys/inventory実行時のオプションを以下のように変えるだけです。
inventoryd --identity <払い出されたデバイスID> --psk <払い出されたシークレットキー(base64)>ちなみにこれまで実行した際に指定していた-bというオプションは、LwM2Mのブートストラップという仕組みを使用したもので、これを使うとSORACOM Airの回線で簡単・安全に認証情報の取得ができます。デバイスごとにパスワードを発行・保存しなくても良いのでとても簡単・便利です。逆に認証情報の発行・保存さえすれば、SORACOM Airの回線でなくとも使用できます。この場合、価格的にはInventoryの使用料金50円だけで済むのでとても安価ですね。
使いどころあるかわかりませんが、PCやサーバに入れても動いて、値の取得や遠隔操作できるようになりますよ(外向きのUDP 5684ポートが空いていれば)
他の遠隔操作方法との比較
ソラコムを使っていると遠隔操作には色々な方法があります。
最近出たSORACOM Napterはとても強力で、遠隔操作はこれで十分、という考えもあると思います。
また、SORACOM BeamとAWS IoTを組み合わせ、MQTTでコマンドを送る、ということもされていると思います。比較ポイントはいくつかありますが、以下のように比較してみました。
項目 Napter Beam + AWS IoT Inventory 価格 300円/月 0.0018円 * コマンド数(AWS IoT部分除く) 50円/月 同期/非同期 同期 非同期 同期 必要サービス SORACOMで完結 AWSと連携 SORACOMで完結 コマンド発行方法 デバイスにサービスが必要(SSHやWebAPIなど) AWSのWebAPIもしくはMQTT SORACOMのWebAPI コマンド発行結果 デバイスのサービス次第 仕組みを考える必要あり WebAPIの実行結果 SORACOM Air回線 必須 必須(BeamではなくKryptonとの組み合わせなら要らない) 必須ではない 価格としてはコマンド発行数が少なければBeam + AWS IoT、コマンド発行数が多ければInventoryが有利です。Napterは必要な時のみ使用するという考えなので、使用が前提となっているサービスより割高と考えられます。
コマンドの発行や結果の受け取りはInventoryが一番簡単だと考えています。SORACOMのAPIを使えばよく、応答も同期的に返ってきます。一方AWS IoTは非同期で、応答を返す場合は応答用のトピックを用意して、それを受信する何らかのサブスクライバを用意して受信する、といったことが必要になり、かなり面倒です。逆にAWS IoTは仕組みさえできていれば、多数のデバイスに非同期にメッセージを送り、非同期に返されたメッセージをまとめて収集することができます。同期処理はデバイスが多くなってくると、ひとつずつメッセージを送って応答を取得するのに時間がかかると予想されます。
また、最初の方にも上げましたが、SORACOM InventoryはSORACOMだけで完結できるサービスです。Napterもそうですね。BeamはAWS IoTと組み合わせることになるので、クラウド用意するの大変、となる可能性があります。AWS IoTはそこそこ難しいサービスですしね。逆にクラウドに慣れているのであれば、Beam + AWS IoTはAWSの様々なサービスと連携できる長所を活かせます。
ということで、所感としてそれぞれの構成で向いているのは、以下のような感じかと思います。
SORACOM Napter - デバイスに人が直接アクセスしてのアドホックな操作
SORACOM Inventory - デバイスの項目を指定しての取得や監視、設定値の変更、定型的な操作
SORACOM Beam + AWS IoT - 定常的な値の取得や多数のデバイスに対する一斉操作すでに他の方法で運用されているところに無理に入れることもないですが、今Harvestで値をとっているだけのところに、ちょっとした制御を入れたい、というような場合にはベストチョイスなのでは無いかと思っています。
おわりに
SORACOM InventoryはIoTデバイスを簡単に管理できるサービスということが感じられましたでしょうか?
inventorydは正直去年公開してからほぼ手つかずですが、また折を見てアップデートしていきたいと思っています。ご要望あればなんらかの形でお伝えください。(このブログへのコメント、GitHubへのIssueなど)そのうち対応するかも知れません。次回はinventorydを開発の技術的な面を書く予定です。
予定タイトル:「SORACOM Inventoryエージェント開発に向けてDTLS、CoAP、LwM2MをGo言語で実装する」
- 投稿日:2020-01-14T06:43:24+09:00
インクリメント・デクリメントの書き方のまとめ(Scala、Java、Rust、C言語、C++、Go言語、PHP、Perl、Python、Ruby、JavaScript)
いろんな言語を触っていると、言語の細かい仕様がだんだんごっちゃになってきてしまいますので、メモです。
インクリメント・デクリメントの有無
あり: Java、C言語、C++、Go言語△、PHP、Perl、JavaScript
なし: Scala、Rust、Python、RubyGo言語は式を構成する演算子ではなく文(statement)という扱いにすることで、インクリメントの演算子としての問題を回避していて、個人的にはちょうどいい仕様に感じます。
ついでに代入演算子も確認しましたが、こちらはだいたいの言語にあるようです。
Scala
- インクリメント・デクリメント演算子はない
- 代入演算子はある
i += 1 i -= 1
i += 1などはi = i + 1などのシンタックスシュガー。参考
Assignment Operators - Expressions | Scala 2.13
Scalaでは、なぜインクリメントやデクリメントができないのか?
Java
- インクリメント・デクリメント演算子は前置・後置ともにある
- 式であり値を返す
- 代入演算子もある
++i; --i; i++; i--; i += 1; i -= 1;参考
Prefix Increment Operator ++ - Java Language Specification
Rust
- インクリメント・デクリメント演算子はない
- 代入演算子はある
i += 1; i -= 1;参考
Compound assignment expressions - Operator expressions - The Rust Reference
なぜインクリメント演算子がないのか?
Why doesn't Rust have increment and decrement operators?C言語、C++
- インクリメント・デクリメント演算子は前置・後置ともにある
- 式であり値を返す
- 代入演算子もある
++i; --i; i++; i--; i += 1; i -= 1;Go言語
- C言語でいうインクリメント・デクリメント演算子は後置のみ
- 式ではなく文の扱いなので、式の中には埋め込めない
- 代入演算子もある
i++ i-- i += 1 i -= 1参考
IncDec statements - The Go Programming Language Specification
++や--が演算子ではない件
演算子とステートメント — プログラミング言語 Go | text.Baldanders.infoPHP
- インクリメント・デクリメント演算子は前置・後置ともにある
- 式であり値を返す
- 代入演算子もある
++$i; --$i; $i++; $i--; $i += 1; $i -= 1;参考
Perl
- インクリメント・デクリメント演算子は前置・後置ともにある
- 式であり値を返す
- 代入演算子もある
++$i; --$i; $i++; $i--; $i += 1; $i -= 1;参考
インクリメントとデクリメント - perlop - Perl の演算子と優先順位 - perldoc.jp
Python
- インクリメント・デクリメント演算子はない
- 代入演算子はある
i += 1 i -= 1累算代入文というらしい。
参考
累算代入文 (augmented assignment statement) - 単純文 (simple statement) — Python 3.8.0 ドキュメント
Ruby
- インクリメント・デクリメント演算子はない
- 代入演算子はある
i += 1 i -= 1自己代入というらしい。
参考
Ruby にインクリメント演算子のようなものが無い理由 - fugafuga.write
Rubyのインクリメント速度のバージョンごとの比較 - Qiita
JavaScript
- インクリメント・デクリメント演算子は前置・後置ともにある
- 式であり値を返す
- 代入演算子もある
++i; --i; i++; i--; i += 1; i -= 1;参考
Update Expressions - ECMAScript® 2019 Language Specification








