일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- CORDA
- 파이썬 머신러닝
- 플레이프레임워크
- 블록체인
- Hyperledger fabric gossip protocol
- 그라파나
- Akka
- Play2 로 웹 개발
- Adapter 패턴
- 엔터프라이즈 블록체인
- 하이퍼레저 패브릭
- 이더리움
- 파이썬
- Actor
- 주키퍼
- 스칼라
- Play2
- 안드로이드 웹뷰
- 파이썬 데이터분석
- 스칼라 강좌
- play 강좌
- 하이브리드앱
- 스위프트
- hyperledger fabric
- akka 강좌
- Golang
- 파이썬 강좌
- play2 강좌
- 파이썬 동시성
- 스칼라 동시성
- Today
- Total
HAMA 블로그
[이더리움에서 배우는 Go언어] select 의 거의 모든 패턴들 본문
go언어에서 가장 재밌으며, 강력한 키워드인 select/채널의 다양한 패턴을 살펴봄으로써 우리의 코딩력을 향상시켜 보겠습니다. 이러한 go특유의 핑퐁스타일의 코딩에 빠져들면 헤어나오기 힘들겁니다 :-)
switch
먼저 형제 관계에 있는 switch를 통해 몸풀기를 좀 하구요.
1. switch (1)
// 일반 switch
func main(){
i := "korea"
switch i {
case "korea":
fmt.Println("korea")
case "usa":
fmt.Println("usa")
case "japan":
fmt.Println("japan")
}
}
switch문은 보통 우리가 생각하는 듯 그러합니다.
2. switch (2)
func main(){
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}
}
비교문을 케이스에 넣을 수도 있습니다.
3. switch (3)
func WhiteSpace(c rune) bool {
switch c {
case ' ', '\t', '\n', '\f', '\r':
return true
}
return false
}
케이스가 리스트가 될 수도 있구요.
4. switch (4)
func main(){
Loop:
for _, ch := range "a b\nc" {
switch ch {
case ' ': // skip space
break
case '\n': // break at newline
break Loop
default:
fmt.Printf("%c\n", ch)
}
}
}
// a
// b
break 문으로 switch문을 탈출 할 수도 있으며, 지정된 위치까지 탈출도 가능합니다. 위의 Loop로 탈출하면 for문을 벗어나게 됩니다.
select
패턴-1
func main() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
for{
time.Sleep(5 * time.Second)
c1 <- "one"
}
}()
go func() {
for{
time.Sleep(10 * time.Second)
c2 <- "two"
}
}()
for{
fmt.Println("start select------------------")
select {
case msg1 := <-c1:
fmt.Println("received", msg1)
case msg2 := <-c2:
fmt.Println("received", msg2)
}
fmt.Println("end select-------------------\n\n")
}
}
case 문에서 채널로부터의 이벤트를 받는데요, 여기서 중요한 것은 case 문의 채널에 값이 들어 올 때 까지 select문에서 블록된다는 점입니다. 앞으로의 모든 패턴들이 이런 느낌에서 출발합니다.
default:
fmt.Println("default")
}
하지만 이렇게 default문을 넣어주면 블록되지 않고 default문을 처리하고 다시 순회됩니다.
패턴-2
package main
import (
"fmt"
"time"
)
func process(ch chan string) {
time.Sleep(10 * time.Second)
ch <- "process successful"
}
func scheduling(){
//do something
}
func main() {
ch := make(chan string)
go process(ch)
for {
time.Sleep(1 * time.Second)
select {
case v := <-ch:
fmt.Println("received value: ", v)
return
default:
fmt.Println("no value received")
}
scheduling()
}
}
어떤 생산자가 결과를 줄 때 까지 기다리는 방식의 코드를 구성 할 수 있습니다. idle 타임에는 주구장장 기다리는게 아니라 어떤 다른 로직을 수행 할 수 도 있을 겁니다. 물론 default가 빠지면 계속 기다리도록 할 수도 있을테구요.
패턴-3
func server1(ch chan string) {
ch <- "from server1"
}
func server2(ch chan string) {
ch <- "from server2"
}
func main() {
output1 := make(chan string)
output2 := make(chan string)
go server1(output1)
go server2(output2)
time.Sleep(1 * time.Second)
select {
case s1 := <-output1:
fmt.Println(s1)
case s2 := <-output2:
fmt.Println(s2)
}
}
case s1 이 선택될지, s2가 선택될지는 모릅니다. 랜덤 선택으로 사용 될 수 있습니다.
패턴-4
package main
import (
"fmt"
"time"
)
func consuming (scheduler chan string){
select {
case <- scheduler:
fmt.Println("이름을 입력받았습니다.")
case <-time.After(5 * time.Second):
fmt.Println("시간이 지났습니다.")
}
}
func producing(scheduler chan string){
var name string
fmt.Print("이름:")
fmt.Scanln(&name)
scheduler <- name
}
func main() {
scheduler := make(chan string)
go consuming(scheduler)
go producing(scheduler)
time.Sleep(100 * time.Second)
}
case <- scheduler:
scheduler 채널로부터 무엇인가 받아 올 수 있다면 (즉 채널에 무엇인가 입력되었다면)
case <-time.After(5 * time.Second):
5초가 지났다면
패턴-5
func main() {
scheduler := make(chan string, 1)
prompt := "HAMA"
select {
case scheduler <- prompt:
fmt.Println("이름은: ", <-scheduler)
case <-time.After(time.Second):
fmt.Println("시간이 지났습니다.")
}
time.Sleep(100 * time.Second)
}
채널에 값이 들어가는 것으로도 case 문을 충족시킵니다.
패턴-6
package main
import (
"fmt"
"time"
)
var scheduler chan string
func consuming (prompt string){ fmt.Println("consuming 호출됨")
select {
case scheduler <- prompt:
fmt.Println("이름을 입력받았습니다 : ", <- scheduler)
case <-time.After(5 * time.Second):
fmt.Println("시간이 지났습니다.")
}
}
func producing (console chan string) {
var name string
fmt.Print("이름:")
fmt.Scanln(&name)
console <- name
}
func main() {
console := make(chan string, 1)
scheduler = make(chan string, 1)
go func(){
consuming(<-console)
}()
go producing(console)
time.Sleep(100 * time.Second)
}
조금 더 응용하여, 아래와 같은 함수에 매개변수로
func consuming (prompt string){
.. }
consuming(<-console)
이런식으로 넣어주는 방식도 있습니다. 참고로 이런 방식일 경우 <- console을 통하여 string 매개변수를 받지 못하는 동안에는 consuming 함수 자체가 블록됩니다.
패턴-7
wg := sync.WaitGroup{}
errC := make(chan error)
done := make(chan bool, maxParallelFiles)
for i, entry := range list {
select {
case done <- true:
wg.Add(1)
case <-quitC:
return fmt.Errorf("aborted")
}
go func(i int, entry *downloadListEntry) {
defer wg.Done()
err := retrieveToFile(quitC, fs.api.fileStore, entry.addr, entry.path)
if err != nil {
select {
case errC <- err:
case <-quitC:
}
return
}
<-done
}(i, entry)
}
for문을 주구장창 도는게 아니라, list만큼만 돌면서 내부의 고루틴을 처리하는데, 위에 select문으로 스케쥴링을 하고 있습니다. done채널에는 maxParallelFiles 만큼만 true가 들어 갈 수 있기 때문에 처리량이 꽉 차면 case done <- true에서 블록되고 다음 고루틴을 시작하지 않게 됩니다. 고루틴 내부의 <- done 을 통해 버퍼가 해소되면 진행됩니다.
패턴-8
package main
import "fmt"
import "time"
func main() {
requestChan := make(chan chan string)
go goroutineC(requestChan)
go goroutineD(requestChan)
time.Sleep(time.Second)
}
func goroutineC(requestChan chan chan string) {
responseChan := make(chan string)
requestChan <- responseChan
response := <-responseChan
fmt.Printf("Response: %v\n", response)
}
func goroutineD(requestChan chan chan string) {
responseChan := <-requestChan
responseChan <- "wassup!"
}
채널에 채널을 넣습니다.
즉 생산자가 소비자한테 채널을 통해 데이터를 보내는게 아니라, 소비자가 생산자한테 이제 나 준비됬으니깐, 니가 만든 것을 내가 준 채널을 통해서 보내줘~ 라는 의미입니다. 적극적인 소비자지요.
아래처럼 select문에서 처리 될 수 있습니다.
collectSig chan chan string
collectSig는 string 타입의 채널의 채널로 정의됩니다.
// 리모트 peer로 부터 내 피어 정보가 요청됨.
func (p * Peer) collect() {
cs := make(chan string)
p.node.pm.collectSig <- cs
collected_peers := <- cs
....
}
collectSig에 string 타입의 채널을 태워 보냅니다.
func (pm * PeerManager) run(){
for {
select {
case collchan := <- pm.collectSig:
collchan <- pm.getCollect()
}
time.Sleep(10*time.Millisecond)
}
}
case문에서 collchan 을 통해 string타입의 채널을 받아서, 그 채널에 getCollect를 통해 얻은 데이터를 보내줍니다.
패턴-8
checkInterrupt := func() bool {
select {
case <-stop:
return true
default:
return false
}
}
stop 이라는 이벤트가 있으면 true 아니면 false를 반환하는 함수
패턴-9
func (t *udp) handleReply(from NodeID, ptype byte, req packet) bool {
matched := make(chan bool, 1)
select {
case t.gotreply <- reply{from, ptype, req, matched}:
// loop will handle it
return <-matched
case <-t.closing:
return false
}
}
t.gotreply 채널에 reply 개체를 넣어줍니다. t.gotreply 채널의 버퍼가 충분해서 들어가면 matched 채널을 통해 데이터가 들어 올 때까지 기다렸다가 리턴해 줍니다. 아마도 t.gotreply 채널에 들어온 reply개체를 통해서 어떤 작업을 하다가 결과로 matched 채널에 값을 넣어 주겠지요. 대기중에 closing 이벤트가 먼저 날라오면 false를 리턴하네요.
패턴-10
ticker := time.NewTicker(time.Millisecond * 50)
for {
select {
case <-ticker.C:
//... 50초에 한번씩 어떤 작업을 합니다 ...
case <-done:
return
}
}
주기적으로 실행되는 로직을 위한 코드입니다. done 채널을 통해 멈출 수도 있습니다.
패턴-11
select {
case <-notifier.Closed():
return
default:
}
함수 호출을 통해서 특정 채널을 리턴 받고, 그 채널에 값이 들어오는 순간을 기다릴수 있습니다.
패턴-12
func (p *Peer) handle(msg Msg) error {
switch {
case msg.Code == pingMsg:
msg.Discard()
go SendItems(p.rw, pongMsg)
case msg.Code == discMsg:
var reason [1]DiscReason
// This is the last message. We don't need to discard or
// check errors because, the connection will be closed after it.
rlp.Decode(msg.Payload, &reason)
return reason[0]
case msg.Code < baseProtocolLength:
// ignore other base protocol messages
return msg.Discard()
default:
// it's a subprotocol message
proto, err := p.getProto(msg.Code)
if err != nil {
return fmt.Errorf("msg code out of range: %v", msg.Code)
}
select {
case proto.in <- msg:
return nil
case <-p.closed:
return io.EOF
}
}
return nil
}
switch 문의 default 문 안에 select 를 위치 시킵니다. msg가 어떤 프로토콜을 위한 것인데 알아낸후에 해당 프로토콜의 in 채널에 msg를 넣어주고 있습니다.
패턴-13
func (rw *protoRW) WriteMsg(msg Msg) (err error) {
select {
case <-rw.wstart:
err = rw.w.WriteMsg(msg)
rw.werr <- err
case <-rw.closed:
err = ErrShuttingDown
}
return err
}
wstart 채널에 값이 들어와서 쓰기가 가능해지면 씁니다. 다 쓰고 난 후에는 werr 채널에 알려주어서 다음 쓰기가 가능하게 합니다. 이 코드에는 안보이지만 다음 쓰기가 가능하게 하는 방법은 wstart 채널에 값을 넣는 것입니다.
패턴-14
func (p *MsgPipeRW) WriteMsg(msg Msg) error {
if atomic.LoadInt32(p.closed) == 0 {
consumed := make(chan struct{}, 1)
msg.Payload = &eofSignal{msg.Payload, msg.Size, consumed}
select {
case p.w <- msg:
if msg.Size > 0 {
// wait for payload read or discard
select {
case <-consumed:
case <-p.closing:
}
}
return nil
case <-p.closing:
}
}
return ErrPipeClosed
}
select 안에 select가 들어 갈 수 도 있습니다. msg를 받아서, p.w채널에 넣어주는데 성공한후에 p.w 채널이 받아서 무엇인가 처리 할 때까지 다음 select문에서 <- consumed: 를 통해서 기다리게 됩니다. 이런 것은 보통 여러 모듈이 공평하게 OS의 I/O를 나누어 같기 위해 사용됩니다. framming이라고도하죠.
패턴-15
type peerOpFunc func(map[discover.NodeID]*Peer)
peerOp chan peerOpFunc
// PeerCount returns the number of connected peers.
func (srv *Server) PeerCount() int {
var count int
select {
case srv.peerOp <- func(ps map[discover.NodeID]*Peer) { count = len(ps) }:
<-srv.peerOpDone
case <-srv.quit:
}
return count
}
채널에는 함수자체를 넣을 수 도 있습니다. 따라서 관심사의 분리/의존성 주입을 이렇게 할 수 있게 되지요.
레퍼런스:
https://github.com/ethereum/go-ethereum
https://golangbot.com/select/
'Go' 카테고리의 다른 글
[Golang] recover 는 언제 사용하나? (1) | 2019.02.22 |
---|---|
고성능을 위한 GO (0) | 2019.02.13 |
[이더리움으로 배우는 GO언어] 자료구조 & 컬렉션 (0) | 2019.01.15 |
[이더리움에서 배우는 Go언어] 강력하게 밀착된 컴포지션 (0) | 2019.01.10 |
[이더리움에서 배우는 Go언어] nat 옵션 이야기 - (2) (0) | 2018.12.14 |