NameError is not defined

LeetCodeを解いてて出てきたエラーです。

下記は適当に書いたものなんですが、insertRowに注目してください。

class Solution:
    def insertRow(self, val: int, depth: int):
        print(val)
        print(depth)

    def addOneRow(self, root: Optional[TreeNode], val: int, depth: int) -> Optional[TreeNode]:
        insertRow(val, depth)    <---- ここ!!
        return root

これを実行してみると

NameError : name "insertRow" is not defined

というエラーがでました。

なんでかなーとすごい悩んだんですが、自分の関数を呼び出すときはselfが必要なんですね。
なので

self.insertRow(val, depth)

で解決しました。

まあそりゃそうだよな...。久しぶりすぎて忘れてた(言い訳)。

stackoverflow.com
まさにこの解答が参考になりました。

オブジェクト指向言語プログラミングを学ぶ時のオススメ本

The Scheme Programming Bee

久しぶりの投稿です。

インフラの仕事をしてますが、あらためてJavaプログラミングを学びなおしたいなと思いました。
そこで最近読んだ本やオススメ本を紹介したいと思います。

「プロになるJava」これ!めちゃ良かったです。
この記事を書いてるのも、たまたま会社でこの本を読んだから「あ、やっぱJavaってええな」と思ったからです。
モダンな書き方が学べてとてもいいです。

もう3版が出てるんですね。私は第2版を読んでいました。
Javaの継承などを学んだはいいけどオブジェクト指向ってどうやって活用するの?と思ったらこれで理解できます。

昔に読んだんですが、これも設計を考える上でとても良かったです。

Javaはだいたいわかるんだけどmavenコマンドがわからないんだよ!と私は悩んでいました。ですがこの本で解決しました。Mavenってすごい便利なんだなとわかりました。オススメ。


番外編

Javaといっしょにドメイン駆動開発も学んだ方がいいらしいです。

Copy spanning locations and/or storage classes could not complete within 30 seconds. Please use the Rewrite method...ってなんだよ

PythonでGCSのファイルをコピーしようとしたらこんなエラーが出ました。

from google.cloud import storage

storage_client = storage.Client()

src_bucket = storage_client.bucket(BUCKET_NAME)
dst_bucket = storage_client.bucket(OUTPUT_BUCKET_NAME)

src_file_name = "hoge.gz"
dst_file_name = "hoge.gz"

src_blob = src_bucket.blob(src_file_name)

new_blob = dst_bucket.copy_blob(src_blob,
                                dst_bucket,
                                new_name=dst_file_name,
                                timeout=180)
new_blob.acl.save(src_blob.acl)

エラー内容

Copy spanning locations and/or storage classes could not complete within 30 seconds. Please use the Rewrite method (https://cloud.google.com/storage/docs/json_api/v1/objects/rewrite) instead.

なんかtimeout=180も効いてないみたいです。

どうやらコピーしようとしているファイルが数GBと少々大きい場合このようなエラーが出るそうです。

メッセージにあるようにrewriteメソッドを使えとあります。

書き直してみました。

from google.cloud import storage

storage_client = storage.Client()

src_bucket = storage_client.bucket(BUCKET_NAME)
dst_bucket = storage_client.bucket(OUTPUT_BUCKET_NAME)

src_file_name = "hoge.gz"
dst_file_name = "hoge.gz"

src_blob = src_bucket.blob(src_file_name)
dst_blob = dst_bucket.blob(dst_file_name)

rewrite_token = None

while True:
  rewrite_token, bytes_rewritten, total_bytes = dst_blob.rewrite(src_blob,
                                                                 token=rewrite_token)
  progress_percent = (bytes_rewritten * 100) // total_bytes
  print(f"Progress : {bytes_rewritten} / {total_bytes} bytes {progress_percent}%.")

  if rewrite_token is None:
    print("Copy has done !!")
    break

実行してみると

Progress : 461373440 / 1899448698 bytes 24%.
Progress : 964689920 / 1899448698 bytes 50%.
Progress : 1509949440 / 1899448698 bytes 79%.
Progress : 1899448698 / 1899448698 bytes 100%.
Copy has done !!

気になったrewrite_tokenはなんかランダムな値でしたので省略します。

qiita.com

ValueError: ('Iterator has already started', <google.api_core.page_iterator.HTTPIterator object at 0xaaaaaaa>)

pythonでCloud Storageのファイル数を数えようとしたらこんなエラーがでました。
なんなんだと。

  File "/........./lib64/python3.6/site-packages/google/api_core/page_iterator.py", line 227, in __iter__
    raise ValueError("Iterator has already started", self)
ValueError: ('Iterator has already started', <google.api_core.page_iterator.HTTPIterator object at 0x7fc844c4aeb8>)

そもそものコードはこちらです。
やりたいことは

  • 対象フォルダのファイルを探す。
  • ファイルが無ければ終了。
  • ファイルがあればファイル名を表示する。

というものです。

from google.cloud import storage
import sys

storage_client = storage.Client()
bucket = storage_client.bucket(BUCKET_NAME)

### 集計対象のファイルを探す
blobs = bucket.list_blobs(
  prefix=TARGET_GCS_FOLDER + '/hoge_'
)

if len(list(blobs)) == 0:
  print("Error, No target files....")
  sys.exit(1)

for item in blobs:  # <--- ここ!!!
  print(item.name)

そんでどうやらエラーの箇所はblobオブジェクトをイテレートしてファイル名を表示するところでした。

なぜかと?

どうやらblobオブジェクトのイテレーターは1回しか使えないみたいです。
File "/........./lib64/python3.6/site-packages/google/api_core/page_iterator.py"についてコードを見てみると

class Iterator(object):
...................
    def __init__(
        self,
        client,
        item_to_value=_item_to_value_identity,
        page_token=None,
        max_results=None,
    ):
        self._started = False # <--- ここ!!
        self.client = client
.......................
    def __iter__(self):
        """Iterator for each item returned.

        Returns:
            types.GeneratorType[Any]: A generator of items from the API.

        Raises:
            ValueError: If the iterator has already been started.
        """
        if self._started:
            raise ValueError("Iterator has already started", self)  # <--- ここ!!!
        self._started = True
        return self._items_iter()

とあります。
一度__iter__が呼ばれてしまうとself._startedがTrueになりValueErrorがなってしまうことがわかります。

ここで最初の__iter__ですが

if len(list(blobs)) == 0:

でリスト数を数えています。このlist(...)で__iter__が呼ばれているので、すでにself._startedがTrueとなっていたわけです。

なので一度別のリスト型変数に入れなおしてからやればエラーになりません。

blob_list_obj = list(blobs)

if len(blob_list_obj) == 0:
  print("Error, No target files....")
  sys.exit(1)

for item in blob_list_obj:
  print(item.name)


qiita.com

シェルスクリプトの変数加工(%と#)について

A shell script wants your job

シェルスクリプトでこんな変数の加工の仕方を知りませんでした。
まず下記のサンプルプログラムを見てみましょう。

$ HOGEHOGE_VAL="aaa/bbb/ccc/ddd/eee"

$ echo ${HOGEHOGE_VAL}
aaa/bbb/ccc/ddd/eee

$ echo ${HOGEHOGE_VAL%/*}
aaa/bbb/ccc/ddd

$ echo ${HOGEHOGE_VAL%%/*}
aaa

$ echo ${HOGEHOGE_VAL#*/}
bbb/ccc/ddd/eee

$ echo ${HOGEHOGE_VAL##*/}
eee

変数の後に%か#をつけて、一致する条件を書けば一致した部分を省いて値にしてくれるんですね。
いやー便利。

% であれば右からの一致を探す。
% 一個であれば最短一致で、%%二個であれば最長一致になります。

# は左からの一致を探します。
%同様に#一個であれば最短一致で、##二個であれば最長一致です。

今までファイルパスを取得してからファイル名を取得する時は、

awk -F/ '{print $NF}'

とかcutでなんとかやってましたがもうそうする必要はないですね。

今後は

echo ${HOGEHOGE_VAL##*/}
eee

でファイル名取得

echo ${HOGEHOGE_VAL%/*}
/aaa/bbb/ccc/ddd

でファイルパスが取得できます。

目からうろこでした。

qiita.com

新しいシェルプログラミングの教科書

新しいシェルプログラミングの教科書

pythonのimportエラー ValueError: Attempted relative import in non-package を雑に解決する

f:id:suganoo:20200423093424p:plain
pythonで悩ましいところの一つにimportエラーがあります。

適切な方法でモジュールをインポートする必要があるのですが、自分で作ったスクリプトを適当に相対パスでimportとしようとするとエラーになって悩むことがあります。

importのパスがよろしくないとこんな感じのエラーが出ます。

ValueError: Attempted relative import in non-package

実行したフォルダより上の階層はセキュリティ上の理由?からかアクセスできない制限があることをしらずなかなかエラーを解消できないことがあります。

参考サイト
teratail.com

そこで思った通りにimportできなかったけども、それを雑に解決した方法をブログに書いておこうと思います。

雑に解決した方法

サンプルコード

ファイル構成は下記のようになってるとします。

import_work/  <-- いまここで作業中
 - animal.py
 - script_main.py
 work_dir/
  - script_sub_1.py
  - script_sub_2.py
ここで同レベルの階層にいるanimal.pyとscript_main.pyについて

まずanimal.pyについては単純にprintするだけのクラスです

# coding: utf-8

class Animal:
  def call(self):
    print "Animal !!!"

それを同階層のスクリプトが実行します。

script_main.py

import animal

anml = animal.Animal()
anml.call()

これを実行すると

python script_main.py

Animal !!!

特に問題ないですね。

一つ階層が下がるとどうなるか?

これを一つフォルダを作ってスクリプトを書いてみます。

今ここで階層を確認しますが、script_sub_1.pyにとってanimal.pyは一つ上の階層になります。

import_work/
  - animal.py
  work_dir/
    - script_sub_1.py  <---

work_dir/script_sub_1.py

from .. import animal

anml = animal.Animal()
anml.call()

なので、冒頭に

from .. import animal

を入れているわけです。

そんでこれを実行してみるんですが、エラーになります。

import_work/   <--- ここで実行
  work_dir/
python work_dir/script_sub_1.py
Traceback (most recent call last):
  File "work_dir/script_sub_1.py", line 1, in <module>
    from .. import animal
ValueError: Attempted relative import in non-package

実行階層がよくなかったかな?と思ってwork_dir/ に移動して実行してみますが、同じようなエラーでした。

import_work/
  - animal.py
  work_dir/   <--- ここで実行
    - script_sub_1.py
python script_sub_1.py
Traceback (most recent call last):
  File "script_sub_1.py", line 1, in <module>
    from .. import animal
ValueError: Attempted relative import in non-package

ちなみに import_work/ で下記のようにimportを変えて実行してみてもエラーとなりました。

import_work/   <--- ここで実行
  - animal.py
  work_dir/
    - script_sub_1.py
import animal  # <---ここを変えた

anml = animal.Animal()
anml.call()
Traceback (most recent call last):
  File "work_dir/script_sub_1.py", line 4, in <module>
    import animal
ImportError: No module named animal

なぜimportできないか?

sys.path を調べてみる

ここでsys.pathを調べてみます。sys.pathはモジュールなどを調べるもとになるディレクトリのリストです。

import_work/
  - animal.py
  work_dir/
    - script_sys_path.py

script_sys_path.py

import sys
print sys.path

この実行結果が下記です。不要なところは伏せさせてもらいます。

['/home/suganoo/import_work/work_dir', ..., '....../python2.7/site-packages']

大事なところは/home/suganoo/import_work/work_dirなのですが、
script_sys_path.pyが存在するimport_work/work_dirフォルダしかパスが通ってないんですね。

=animal.pyが存在する/home/suganoo/import_work/にパスが通ってないわけです。

なので一つ上の階層のanimalモジュールが読み込めない。

自分も知らなかったのですが、sys.pathって実行してるファイルが存在する階層以降しか参照できないことがあらためて知りました。

一つ上の階層を入れる

ここで何とか一つ上の階層を入れ込んでみます。
import を少し変えたscript_sub_2.py を作ってみました。

import_work/
  - animal.py
  work_dir/
    - script_sub_2.py

script_sub_2.py

# coding: utf-8
import os
import sys

# 以降のimport のために一つ上の階層パスを追加
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../")

#----------------
import animal

anml = animal.Animal()
anml.call()

これを実行すると

python work_dir/script_sub_2.py
Animal !!!

ちゃんと意図通りに実行できてますね。

なにをやってるか

気になるのがsys.path.append...のところですよね。

sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../")
  1. __file__ は実行中のファイル名が入ります。なのでscript_sub_2.py
  2. os.path.abspath(__file__)は実行中ファイルの絶対パスが取得されます。→/home/suganoo/import_work/work_dir/script_sub_2.py
  3. os.path.dirname(os.path.abspath(__file__))は実行中ファイルのパスが取得されます。→/home/suganoo/import_work/work_dir/
  4. os.path.dirname(os.path.abspath(__file__)) + "/../"で一つ上の階層を指定します。→/home/suganoo/import_work/work_dir/../

そこまで取得できたパスをsys.pathに追加したのが下記になります。
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../")

これを実行したときのsys.pathは下記の通りです。

[...., '/home/suganoo/import_work/work_dir/../']

一番最後に指定した上の階層を指定するパスが含まれています。
なので実行できたわけですね。


どうしてこういう書き方にしたかというと
どこから実行してもそのファイルの一つ上のモジュールを実行できるようにしたかったんです。
そのフォルダに入らないと実行できない、というのはあまり使いやすいとは言えないでしょう。

このことを考えたのは、testsフォルダで単体テストスクリプトを書いてるときに実行する場所を制限されたくないな=どこからでも実行できるようにしたいなと思ったのがきっかけでした。

ただこのように一つ上の階層をsys.pathに追加するのは非推奨らしいので知っておきましょう。



このようにimportのエラーを無理矢理雑に解決してみたお話でした。


参考
okuya-kazan.hatenablog.com

シェルスクリプトでブロックコメント

f:id:suganoo:20200124175452p:plain
シェルスクリプトでブロックコメントを作る方法を知ってますか?

ブロックコメントはpythonでいう"""(ダブルクオート3つ)や'''(シングルクオート3つ)で囲んだコメント、またjavaで言えば/*.....*/といったように書くコメントです。

1行コメントは # でコメントアウトすればいいですが、何十何百行にもわたる行をコメントアウトするにはどうしたらよいでしょうか。  

シェルスクリプトではブロックコメントとしての機能は無いようです。

ヒアドキュメントの機能を使ってブロックコメントとしているようです。

ヒアドキュメント

ヒアドキュメントとは何か?

スクリプトに書いたものを、標準出力として扱う機能です。

cat を例に下記のように実行すると、

cat << END
Hello
Hi
END
Hello
Hi

と出力されます。

ENDとENDで囲まれた内容が、スクリプトファイルに記述されているのに標準入力としてcat に渡され文字列が出力されているわけです。

ちなみにヒアドキュメントでは、<< (終了文字列).......(終了文字列) の形式で書けばよく、この(終了文字列)はENDでないとダメ、というわけではありません。

ブロックコメント

そこでシェルプログラミングではヒアドキュメントを利用してブロックコメントを記述します。

上記の例ではヒアドキュメントの入力の渡し先がcatコマンドでしたが、別に何もなくても構いません。

<< COMMENT
Hello
Hi
COMMENT

このように書けばCOMMENTで囲まれた内容がヒアドキュメントとして扱われ、かつ入力の渡し先が何もないのでなにもされない、つまりブロックコメントのような存在になってくれます。

ちょっと特殊なやりかたですね。

シェルスクリプトで可変長引数を扱う

f:id:suganoo:20200124175712p:plain
シェルスクリプトを実行するときに、いくつになるかわからない引数を全て使いたいときがあります。
例えば作ったファイルを全て何か処理したいとき、いくつの引数になるかわかりません。

そういったときに可変長引数を使うと便利です。

可変長引数は ${@} ですべての引数を扱うことができます。
1つづつ扱いたければ下記のようにループも使えます。

for N in "${@}" ;
do
  echo ${N}
done

抽出することも可能で、例えば2番目から3つ値を取得したいときは

# a b c d e f の時
echo "${@:2:3}"   # --> b c d

で取得することができます。

これと引数の数を表す $# を組み合わせれば、引数の最後2つだけ取得するといったこともできます。

# a b c d e f の時
echo "${@:($# - 2):2}"   # --> e f

こんな感じですね。

#* も同じように扱えますが、少し挙動が違います。

下記に実行例を書いてみました。参考にしてみてください。

testtest.sh
--------------
echo "\${@} : "${@}
echo "\${@:4:3} : "${@:4:3}
echo
echo "\${*} : "${*}
echo "\${*:4:3} : "${*:4:3}
echo
echo "loop check \"\${@}\""
for N in "${@}" ;
do
  echo ${N}
done
echo
echo "loop check \"\${*}\""
for N in "${*}";
do
  echo ${N}
done
echo

NUM=2
echo "2 items from last  \${@:(\$# - ${NUM}):${NUM}} : "${@:($# - ${NUM}):${NUM}}

実行結果

$ sh testtest.sh 1 2 3 4 5 6 7 8 9 10 11 12
-----------
${@} : 1 2 3 4 5 6 7 8 9 10 11 12
${@:4:3} : 4 5 6

${*} : 1 2 3 4 5 6 7 8 9 10 11 12
${*:4:3} : 4 5 6

loop check "${@}"
1
2
3
4
5
6
7
8
9
10
11
12

loop check "${*}"
1 2 3 4 5 6 7 8 9 10 11 12

2 items from last  ${@:($# - 2):2} : 10 11

数値計算するbcコマンドが便利

こんにちは suganoo です。

シェルスクリプトを書いてたりLinuxのコマンドをたたいてると単純な計算をしたいときがあります。

単純な足し算やfor文内のインクリメントとか。

そんなんであれば $((...)) でどうにかなります。

例えば足し算なら

CNT=$(( CNT + 1 ))

とか

forループとかだとこんな感じ

CNT=0
for MUDA in {1..10}
do
  CNT=$(( CNT + 1 ))
done
echo ${CNT}

ですが、例えば wc -l でいくつかのファイルの行数を取って来てて合算したいとか、du -sm でファイルサイズ取って来てサイズを合算したいときとか、ちょちょっと計算したいときがあります。

それもわざわざ計算機アプリ起動したくないし、Chromeで打ち込むとかしたくない。

それをコマンドでできないか?


そんな時にbcコマンドが便利です。

使い方は簡単で echo に計算式を出力してやってパイプでbcに流せばいいです。

$ echo "1 + 3" | bc
4

小数点はscaleで指定すればOK

$ echo "scale=5; 7 / 6" | bc
1.16666

いやーこの小数点指定をお手軽にできるのはシェルスクリプト書くときに便利なんですよ!

他にもパラメーターに -l を付ければ、sin, cos, arctan, log, exp(自然対数)、sqrtも使えます。

関数 意味
s() sin
c() cos
a() arctan
l() log
e() 自然対数のe
sqrt() 平方根

man bcで調べると色々出てきます。

$ echo "s(1)" | bc -l
.84147098480789650665

他にも変数も使えます。

$ echo "a=3 ;b=2 ;a+b" | bc
5

bcコマンドをそのまま実行すればインテラクティブモードで使えます。

$ bc
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
1 + 2
3
quit

exprコマンドより使いやすい感じしますねー

シェルスクリプトで配列を扱う

こんにちは suganoo です。

シェルスクリプトを書いていて配列ってどうやるんだっけ?っと

たびたび調べることが多いので記事に書いておこうと思います。

コマンドの結果を配列にする

まずコマンドの結果を配列に入れる方法

FILE_LIST=(`ls`)

コマンドを``で囲んで、かっこでくくればいいですね。
はい、そんだけ

配列のループ

配列のループは配列番号の代わりに@にすればいいです

for FILE in ${FILE_LIST[@]}
do
....
done

サンプルコード

サンプルコードを書いてみます
カレントディレクトリ配下にあるファイルをリストアップする処理です。

#!/bin/bash

FILE_LIST=(`ls`)

for FILE in ${FILE_LIST[@]}
do
  echo "${FILE}"
done


簡単ですねー


FILE_LIST=(`ls`) のようにコマンドの結果を配列型として変換できるのをよく忘れていました。

これでもう忘れてないはず

速度制限するパッケージ golang.org/x/time/rateについてQiitaに書いてみた

こんにちは suganoo です。

ここ最近3月4月とブログをあまり更新してませんでした。

というのも「Go言語による並行処理」が勉強になったと以前投稿したのですが、
読んでるだけじゃだめだ!やっぱ写経しないと!っと思ってサンプルコードを写経してたわけで更新してませんでした。

以前の記事はこれ
Go言語 channel の使い方を勘違いしてたこと - S氏はたまにblogを更新してます
「Go言語による並行処理」を読んでみたけどめちゃ勉強になる本だった - S氏はたまにblogを更新してます


いやーほんと写経やってみてわかるんですけどね

写経はかなり力がつく!理解が深まる!

っと思います。

細かいところの書き方とかが、あーこういう風に書いてるのねとわかるので、ほんと勉強になるなと思ったソースは写経してみることをオススメします。

写経してみた成果物はこちら
github.com

写経してみてわかったんだけど、本家のサンプルコードでも一部作りかけで動かないコードがあったのでPR作ってみました。更新してないので、たぶん取り込まれないと思う。
github.com


んで、写経してて後半の方に入ると流量制限できるサンプルがありました。
golang.org/x/time/rate というパッケージです。

ざっくりいうと1秒間に10回の速さで実行をさせたい、とか言った時に使えるパッケージです。

このrateパッケージがなかなか面白かったので動作を調べてQiitaに投稿してみました。

qiita.com


いろいろ本を読んで興味あるところに手を出してみると面白いものがいろいろ発見できますね。

またたくさんの本を読むより、一冊の本を写経なりしてしっかり読み込む方が血肉になって力がつくことがよくわかりました。

うん、やっぱ手を動かすのが大事ですね~

Go言語による並行処理

Go言語による並行処理

  • 作者: Katherine Cox-Buday,山口能迪
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2018/10/26
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る

Go言語でのBasic認証とDigest認証のヒント

f:id:suganoo:20190425131003p:plain
久しぶりの投稿でございます。

たまたま社内のjiraにAPIでissueを作ろうと思ったのですが、
その前のプロキシーサーバーがDigest認証になってました。

はてと困りました。

結局はプログラムで解決するのは難しそうなので、直接curlでどうにかしようと思いましたが
それにいたるまでプログラムで解決する方法がGitHubで見つけちゃったりと
資料がみつかったので書いておきます。

概要の理解
madalinazaharia.com

プロキシのBasic認証はこれでいけそう
qiita.com

Goでhttpリクエストする
qiita.com

GoでDigest認証する
このソースコードはほんと参考になりました!
時間があればこれをもとにして書いてみたかった...。
github.com

ヘッダー情報の確認
unarist.hatenablog.com


なるほど勉強になりますね。

Go言語 channel の使い方を勘違いしてたこと

「Go言語による並行処理」を写経してるんですが、チャネルのところで理解が甘かったところがありました。

やってみて、ああそうだったのか!?と気づきました。

わかってなかったことは下記の2つ

  • チャネルってgoroutineで使うもの。
  • そのgoroutineの中でcloseしても大丈夫(むしろこうするべき)

いやーいい加減に理解してはダメですね。

チャネルを閉じて値を取得してみる

サンプルコードを見てみましょう。「Go言語による並行処理」から抜粋してみます。

サンプル01
https://play.golang.org/p/AjTlu6R4K-p

package main

import "fmt"

func main() {
        intStream := make(chan int)
        close(intStream)
        integer, ok := <- intStream
        fmt.Printf("(%v): %v", ok, integer)
}

結果は下記になります

(false): 0

なにも値をいれてないチャネルから値を取り出しても大丈夫だよ、ということです。

main() でチャネルに値を入れてみる

ではここで intStream チャネルになにか値を入れてみましょう。
そのために close(intStream) もdefer にします。

サンプル02
https://play.golang.org/p/3EemcCFsELK

package main

import "fmt"

func main() {
	intStream := make(chan int)
	defer close(intStream)
	intStream <- 3
	integer, ok := <- intStream
	fmt.Printf("(%v): %v", ok, integer)
}

結果は下記になります。

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
	/tmp/sandbox674957142/main.go:8 +0x80

え!?と思いましたがゴルーチン以外で値を入れるとデッドロックになるんですね!

ゴルーチンから値を入れてみる

ではここで intStream チャネルをgoroutine から値を入れてみます。

サンプル03
https://play.golang.org/p/ZZRzjnqnz2v

package main

import "fmt"

func main() {
	intStream := make(chan int)
	defer close(intStream)
	go func() { intStream <- 3 }()
	integer, ok := <- intStream
	fmt.Printf("(%v): %v", ok, integer)
}

結果です

(true): 3

うまくいきましたね。

ゴルーチンでチャネルを閉じてみる

ではここで defer close(intStream) を goroutine に移してみましょう。

予想として、goroutine の中でチャネルを閉じてしまったら受け取るときにエラーなるんじゃないか!?っと思ってました。
だってチャネルを閉じてるんだもん。

サンプル04
https://play.golang.org/p/BfrB7K1g8QR

package main

import "fmt"

func main() {
	intStream := make(chan int)
	go func() {
		defer close(intStream)
		intStream <- 3
	}()
	integer, ok := <- intStream
	fmt.Printf("(%v): %v", ok, integer)
}

結果です

(true): 3

エラーにならず正常な結果ですね!

待受け側 for ... range チャンネルは事前にチャンネルのcloseが必要

あともう一つチャンネルの close で気になった性質。

チャンネルに値を入れた後に、そのチャンネルから値を取得する場合って for ... range で値を取得する場合がありますよね。
下記のサンプルを見てみてください。

サンプル05
https://play.golang.org/p/aA0t3EVTCxn

package main

import "fmt"

func main() {
	// 値が入ったチャンネルを返す関数
	f := func() chan int{
		// チャンネル作って
		intStream := make(chan int)
		
		// ゴルーチンの中で値入れて
		go func() {
			defer close(intStream)
			intStream <- 3
		}()
		
		// チャンネルを返す
		return intStream
	}
	
	ch := f()
	// range で値を取得
	for integer := range ch {
		fmt.Printf("%v ", integer)
	}
}
3

結果はちゃんと取得できます。

この時ゴルーチンの中のチャンネルcloseしなかったらどうなるのかな?と思いました。

サンプル06
https://play.golang.org/p/PYvnH83M8Oe

package main

import "fmt"

func main() {
	// 値が入ったチャンネルを返す関数
	f := func() chan int{
		// チャンネル作って
		intStream := make(chan int)
		
		// ゴルーチンの中で値入れて
		go func() {
			//defer close(intStream)    <-- これな!!
			intStream <- 3
		}()
		
		// チャンネルを返す
		return intStream
	}
	
	ch := f()
	// range で値を取得
	for integer := range ch {
		fmt.Printf("%v ", integer)
	}
}

結果はこれでした

3 fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
	/tmp/sandbox416365219/main.go:23 +0x100

デッドロックエラーになるんですね。
考えてみればそりゃそうですよね。for で値取得をずーっと待つことになるんですから終わりきらないのでdeadlock。当たり前だ。

ではこの値取得を <- で取得したらどうなるのか?

サンプル07
https://play.golang.org/p/d9AjTBsc3Q6

package main

import "fmt"

func main() {
	// 値が入ったチャンネルを返す関数
	f := func() chan int{
		// チャンネル作って
		intStream := make(chan int)
		
		// ゴルーチンの中で値入れて
		go func() {
			//defer close(intStream)    <-- これな!!
			intStream <- 3
		}()
		
		// チャンネルを返す
		return intStream
	}
	
	ch := f()
	// <- で値を取得
	num := <- ch
	fmt.Printf("%v ", num)
}
3

値はちゃんと取れますね。<- で値を取得するだけであればチャンネルをcloseしなくても値取得できるんですね。
チャンネルをちゃんと閉じないのはあまりよろしくないですが。。。

チャネル所有するゴルーチンはチャネルを閉じる責任がある

「Go言語による並行処理」のP77では、ざっくりですが、チャネル所有するゴルーチンはチャネルを閉じる責任があるそうです

そうすることでチャネル所有者がチャネルを閉じるので、閉じたチャネルに書き込んでしまったり、2度チャネルを閉じてしまったり、といったpanicを引き起こすことがなくなります。

なるほどーチャネルってどこでも使えるのかと思ってましたが、そうでもないんですね。
勉強になりました!

Go言語による並行処理

Go言語による並行処理

  • 作者: Katherine Cox-Buday,山口能迪
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2018/10/26
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る

UdemyのgRPCコースやってた

久しぶりの更新になりました。

2月半ばにこんな企画を見つけたのでUdemyやってました。
zine.qiita.com

Udemyって年末年始に90%オフセールしたりして安くなるんですが、買った後なにもやらずにそのままにしちゃうんですよね。

いわゆる積んdemy。

それをこの機会にやってみました。

やってみたのはこれ
gRPC [Golang] Master Class: Build Modern API & Microservices

せこせこ写経してQiitaに投稿してみました。
qiita.com

時間に追われてたとはいえかなり勉強になりました。


それにしてもUdemyってマイナーな技術でもコースがあったりするからいいです。

仮想通貨が出たときもいち早くEthereumのコースが出てたしな。

セールもたびたびやってるのでちょこちょこ探してみるのおすすめです。
Udemy

blog.suganoo.net