サクサク読めて、アプリ限定の機能も多数!
トップへ戻る
WWDC25
qiita.com/ruiu
Deleted articles cannot be recovered. Draft of this article would be also deleted. Are you sure you want to delete this article?
Goroutineを複数使って並列で処理を行って、それがすべて完了したら次に進みたいとしよう。Goroutineの完了はそれを生成したgoroutineに通知されるわけではないので、メインのgoroutineは何らかのメカニズムを使って全員が完了するまで待って、全員が完了したら実行を再開する必要がある。 sync.WaitGroupは複数のgoroutineの完了を待つための値だ(Javaを知っていれば、java.util.concurrent.CountDownLatchによく似ている)。WaitGroupの値に対してメソッドWaitを呼ぶと、WaitGroupが0になるまでWaitはブロックされる(待たされる)。従って、やりたい処理の数だけWaitGroupの値をインクリメントしておいて、処理完了時にデクリメントすれば、Waitを呼んで処理完了を待っているメインのgoroutineは、
Goroutineには内部的にIDが振られているけど、そのIDをGoから取得するAPIは提供されていない。一般に複数のgoroutineが走っている時、今どのgoroutineから実行されているのかを実行時に知る方法はない。 これが不便だという話がたまに出てくるようだ。こないだはデバグ時などに使うためにgoroutineに名前をつけられるようにしようという提案がgolang-devに投げられていた。 これはリジェクトされたのだが、なぜかというと、そういう機能は確かに便利でもあるだろうけど、それを提供するとgoroutineの状態に依存したコードを書くのにも使われてしまうから。 Goroutineにローカルな状態に依存したコードは安全ではない(1つのgoroutineからしか呼べない)。たとえば「UIのAPIはメインスレッドから呼んでください」というふうな感じになる。これはgoroutine
Deleted articles cannot be recovered. Draft of this article would be also deleted. Are you sure you want to delete this article? 以前に8ccというCコンパイラをゼロからひとりで開発していたときのログです。40日でセルフコンパイルできるところまで到達しています。日付はすべて2012年です。コードとヒストリはすべてGitHubで見れます。 3月4日 というわけでコンパイラを作っているわけだけど、1000行くらい書いたらそれなりに動き始めてきた。こんなのも動くし: int a = 1; a + 2; // => 3 こういうのも通る。 int a = 61; int *b = &a; *b; // => 61 文字列は文字の配列として扱っていて、配列をポインタに成り下
構造体を定義して、それに対してメソッドを定義して、最後にその構造体をアロケートして初期化するNewなんとかという関数を用意する、というのを何の疑問も持たずに行っているならちょっと考えものだ。途中まではよいが、Newなんとかみたいなのは別に必須ではない。 メソッドとゼロ値 Goでは新しい値は「ゼロ値」で自動的に初期化される。ゼロ値は型ごとに違うが、数値なら0、文字列なら空文字列、ポインタやインターフェイスならnil、といった具合の値だ。構造体ならそれぞれのフィールドがゼロ値で初期化される。 メソッドはそのレシーバーの値のゼロ値に対して問題なく動くように書くほうがよい。構造体Tを割り当てて初期化する関数としてNewTみたいな関数を用意するのは、本当に初期化が必要なとき以外はやらないほうがよい。 なぜか、というといくつか理由がある。 NewTの代わりにnew(T)を使うようにすると、エクスポート
Goを書いていてrecoverを使うことはまずほとんどない。頻繁にrecoverを書いているとしたらなにかが間違っているのでプログラミングスタイルを見直すこと。 Goでのエラーハンドリング Effective Goなどで説明されているように、Goではエラーは関数の返り値として返される。たとえばio.ReaderのRead関数は、読み込んだバイト数と、(nilかもしれない)エラーの2つの値を返す。Goでは基本的に、エラーは常にこういう通常の値としてハンドルするべきで、エラーの時のための特別な制御構造(try 〜 catch)のようなものを使うのは、利点より害のほうが多いという考え方をとっている。 (同じような考えで例外を使用禁止にしている大規模C++プログラムはいくつもある。たとえばChromiumなどはそうだ。LLVM/Clangもパフォーマンス上の問題で例外を使っていない。C++コンパイ
Goにはガベージコレクションがあるのでプログラマがメモリ管理を意識することは少ない。とはいえ無駄にメモリを割り当てまくるとそれだけメモリアロケータとガベージコレクタが走る回数が増えてプログラムが遅くなってしまう。効率の良いプログラムを書こうと思ったらある程度はどういうふうに値がメモリ上に確保されるのか意識することが必要だ。 メモリシステムへの負荷を下げるにはメモリの量だけではなく回数を減らすことが有効である。Goでは構造体のレイアウトは自分で制御できるし、interior pointer(オブジェクトの内部を指しているポインタ)を取得することもできるので、必要な値をまとめてメモリ上に確保することができる。具体的には次のように行う。 encoderという型がbufというフィールドを持っているのだけど、encoderは同時にscratchという80バイトのバッファのフィールドも持っている。ne
Goのリフレクション(reflect)を使うと構造体のフィールドのリストやインターフェイスのメソッドセットを取得することができる。しかし定義されている関数のリストを取得することはできない。reflectはそういう機能を提供していないのである。 どうも、設定ファイルから関数名を読んで、その関数を呼び出すといったことをやりたいといったような話が定期的にあがってくるようだ。たしかにそういうことができれば便利かもしれないが、そういう機能を提供していないのにはきちんとした理由がある Goでは使われていない関数は最終的なバイナリには入らない。定義されているけどどこからも呼ばれていない関数というのは最初から書いていないのと同じだ。逆に言うと、コンパイラとリンカに関数を削除するという最適化を許すためには、関数一覧を取得するという機能があったりすると困るのである。 では実際に使われている関数だけの一覧を取得
posted articles:Go:91%C:3%compiler:3%プログラミング:3%テスト:3%
Goで複数の仕事があるとして、goroutineを新たに作成してそれに1つ1つの仕事を任せるのは普通のやり方だと思う。たとえばクライアントからの接続を待っているサーバをGoで書いているとして、クライアントからのリクエストごとにgoroutineを起動してその後のやりとりを任せるといったようなパターンは普通だ。 そういったパターンでリクエストが多いと、どんどんgoroutineが起動してしまってメモリが足りなくなることがある。そういった場合に、goroutineの起動を待たせて、最大でもある一定数以上はgoroutineが並行して走っていないようにしたいことはよくあるのだが、そのためにはどうしたらよいだろうか? 一般的なやり方はチャネルを「残りいくつのgoroutineを起動してよいか」という値(セマフォ)として使う方法である。 チャネルを作成するときにチャネルのバッファサイズを決めることが
チャネルに何かの値を送信したいけど、チャネルのバッファがすでに一杯ならそれ以上は送りたくない(送信待ちでブロックしたくない)という状況がある。 たとえば設定の再読み込みの責任を担っているgoroutineがいるとしよう。そのgoroutineは普段はあるチャネルで何か値が送られてくるのを待っていて、値が送られてくると動作を再開して、設定をどこかから読み込む(そのあとロックを獲得してグローバルな設定データを更新するというのが普通だが、それはここでは関係がない)。 さて、そういうgoroutineでは複数回連続して動作するのは通常特に意味がない。従って、設定再読み込みリクエストは1個でも2個でもどちらでもよくて、むしろ複数個連続してチャネルに値が送信されても1個しかキューされないようにしたいだろう。 そういうふうな動作を実現するには下のコードのようにすれば良い。
Goの正規表現はRuss Coxさんの書いたre2が元になっている。re2はよくできた正規表現エンジンなのだけど、一つほかの正規表現エンジンに見られる機能が欠けている。バックリファレンスがないのだ。 ほかの正規表現エンジンではカッコでキャプチャした文字列を番号を指定して正規表現中でもう一度使うことができて、/(\w+) \1/というようなパターンを書くことができる。このパターンは(\w+)にマッチするもの(たとえば単語)に続いて空白文字列があって、さらに()の内容と同じ文字列があること、という条件になる。たとえばこれは"foo foo"はこのパターンにマッチするけど(最初の"foo"が繰り返しているので)、"foo bar"はマッチしない("foo"と"bar"は違うので)というわけだ。 バックリファレンスがないのは欠点に思えるが(実際に不便なときもないわけではないが)、しかしこれはGoの
Goでは一応エスケープ解析をしていて、小さな値はnewで割り当ててもリテラルの構造体として書いても、スタックに割り付けることが可能ならスタックに割り付けることになっている。 スタックは関数呼び出しがリターンすると捨てられる(ゴミ扱いになる)領域なので、値をそこに割り当てるのは、ヒープに割り当てるのに比べて大変効率が良い。割り当てるのも速いし、GCがあとで回収する手間もかからない。 (ただしそういう値が関数からリターンした後に参照されては困るので、そういう値へのポインタなどが関数から逃げ出さないことをコンパイル時に静的に検証している ―― それがエスケープ解析。) しかし大きな値(数百バイト〜)は、エスケープしないとしてもヒープに割り当てられてしまう。そうでなければスタックが成長しすぎて、それはそれでまた問題になるからだ。 頻繁に呼び出される関数で大きな値をテンポラリに使っていると、GCで回
Goではintのサイズはアーキテクチャによって異なり、32ビットか64ビットのどちらかということになっている。32ビットのx86では32ビットだし、x86-64では64ビットだ。ときどきビット演算などでintのサイズがどちらなのかを知りたいことがある。そういうときにはどうすればいいだろうか? 結論から先に言うと次の式を使うとintSizeはアーキテクチャに応じて32か64のどちらかになる。 uintとintは最上位ビットが符号ビットとみなされるかどうかという違いがあるだけで、同じサイズであることが保証されている。uint(0)は型なしの定数0をuintに型変換しているので、その値は32ビットか64ビットのどちらかの符号なし整数0になる。^は単項演算子のXORなので、ビットが反転されて、^uint(0)は32ビットか64ビットのすべてのビットが1の値ということになる。これを63ビット右にシフ
CやC++ではatexit関数で関数を登録しておくと、プログラムの終了時にその関数を自動的に走らせることができる。そういう機能はRubyやPythonにもある。 Goにはそういう機能はない。実装を忘れているのではなくて、意図的にそういう機能を持たせていないのだ。これについてIan Lance Taylorさんが大変説得力のある説明をしていた。 まず第一に、どんなプログラムでも任意の箇所でクラッシュしうるし、まったくバグのないプログラムでもいきなりkillで殺されたりマシンが電源断で落ちるということがある。従ってどんなプログラムも、突然終了させられたあとに、もう一度きちんと動くことができなければならない。つまりatexitはきれいに終了するための機能ということで、atexitが呼び出されないとうまく動かないプログラムというのはそもそも間違っているということになる。 大きなC++プログラムでは
Goは言語機能として並列実行をサポートしているけど、Goで書いたからといって自動的にデータ構造がスレッドセーフになるわけではないので、スレッド安全性を気にしなければならないはこれまでの言語と変わらない。どういうケースが良くてどういうケースがダメなのかを理解していないと安全なプログラムは書けない。それについて説明をしよう。 まず第一にEffective Goのこの一文は覚えておこう。 Do not communicate by sharing memory; instead, share memory by communicating. メモリを共有することで通信しようとしないこと。代わりに通信することでメモリを共有すること。 変数の値を変更したあとにチャネルなどを使わずに、おもむろに別のgoroutineからその変数の値を読み書きしてはいけない。そういうやり方だと読み書き操作の前後関係がき
Goで、文字列、インターフェイス、チャネル、マップ、スライスのポインタを取っているプログラムは、書いた本人がきちんと自分がなにをしているのか理解しているのでなければ、ほぼ確実に間違っているといっていい。 Goではある種の型の値はそもそもポインタのようなものである。上記の型はどれも任意の大きさになり得るが、大きくなりうる実体のデータはヒープに確保されていて、値そのものが持っているのはそのヒープ上への値へのただのポインタ+多少の付随的なデータにすぎない。こういった値を値渡しではなくポインタ渡しする必要はない。ポインタのデリファレンスのほうがポインタのコピーより高くつくし、余計な混乱を引き起こすだけだからだ。もしこういう値をポインタ渡ししているとしたら、そのコードはなにか深い意味があるのではなく、それを書いた人が大きな値がコピーされると勘違いしていて書いた可能性のほうがずっと高い。 文字列は2ワ
このページを最初にブックマークしてみませんか?
『@ruiuのマイページ - Qiita』の新着エントリーを見る
j次のブックマーク
k前のブックマーク
lあとで読む
eコメント一覧を開く
oページを開く