42
---------- 1 동시성 프로그래밍 기초 in Go https://fb.com/me.adunhansa https://twitter.com/arahansa http://adunhansa.tistory.com/ 사이트 : http://arahansa.com ABOUT CONTACT SOURCE 1

동시성 프로그래밍 기초 in GO

  • Upload
    -

  • View
    854

  • Download
    2

Embed Size (px)

Citation preview

---------- 1

동시성 프로그래밍 기초 in Go

https://fb.com/me.adunhansahttps://twitter.com/arahansahttp://adunhansa.tistory.com/

사이트 : http://arahansa.com

AB

OU

T

CO

NTA

CT

SOU

RC

E

1

1. 작성자 소개 2

아라한사

-----------------------

-------------------------------------------- 평소 정리를 즐겨합니다. 한 때 동영상강좌도

만들다보니.. 비실명과 만화캐릭터를 쓰네요

양해 부탁드립니다 ㅎㅎ

출처 표기 3

• 본 PPT는 이 사이트 (http://www.nada.kth.se/~snilsson/concurrency/ ) 의 내용을 기초로 만들어졌습니다.

• CCA 3.0 에 따라 출처를 표기합니다.

만든이유&주의사항? 4

• 음.. 왜인지는 모르겠지만, 괜히 동시성 공부를 하고 싶어서 동시성 공부를 하게 되었습니다-_-; (그래서 골라본게 Go…)

• 뭐.. 공부를 하기 위해 만들어진 것이고, 블로그 아티클을 번역한 것입니다.. GO를 좀 사용하거나 동시성을 이미 좀 보신 분들에게는 기초적인 자료이겠지만(-_-;) 이렇게 정리를 하는데 의의가 있다고 생각합니다~

• 소스깃헙은 여기에.. https://github.com/arahansa/learn_go_concurrent

• 흠.. 원래 따로 하던 스터디에서 말로 하는 것 위주라, 음..소스결과를 다시 좀 넣고 했습니다-_- 원문도 같이 넣으니 애매한 번역은 알아서 봐주세요^^;

• 7번의 data race 감지(-race) 신기하고, 좋습니다. 다른 언어는 어떤지 순수하게 궁금하네요.

목차 5

•1. Threads of execution

•2. Channels

•3. Synchronization

•4. Deadlock

•5. Data races

•6. Mutual exclusion lock

•7. Detecting data races

•8. Select statement

•9. The mother of all concurrency examples

•10. Parallel computation

1. 쓰레드 실행 2. 채널 3. 동기화 4. 데드락 5. 데이터레이스 6. 상호배제 락 7. 데이터 레이스 감지 8. Select구문 9. 모든 동시성 예제의 어머니(?) 10.병렬 컴퓨팅

-----------------------

서문 6

• Go로 동시성 프로그래밍에 대한 소개를 합니다.

다루는 것들 ::

• 동시 쓰레드 실행(Go루틴)

• 기본 동기화 테크닉(채널과 락)

• GO 에서의 기본 동시성 패턴

• 데드락과 데이터 레이스

• 병렬 컴퓨팅

시작하기전에 Go에 대한 배경지식이 있어야 합니다. 아, Go Tour 정도 들어갔다 오시면 도움이 됩니다

1. Threads of Execution 7

• Go는 새로운 쓰레드가 실행하는 것을 허용합니다. 이 새로운 쓰레드는 Go에서는 GO루틴으로 불리고, Go 문법을 이용해서 시작합니다. GO는 새롭게 만들어진, 다른, 고루틴을 실행시킵니다.

• 하나의 단일 프로그램에서 Go루틴은 같은 주소값(address space)를 가집니다.

• 고루틴은 경량쓰레드고, 스택 공간할당보다 조금 더 나갑니다. 스택은 작게 시작해서, 할당에 의해 , 필요하면 힙저장공간을 해제하면서 커집니다. 내부적으로 고루틴은 멀티플렉스드(다중화?) 됩니다 onto 멀티플운영체제쓰레드(주 : 다음 문장을 보면 무슨 말인지 유추가 된다. )

• 만약 하나의 GO루틴이 OS 쓰레드를 블락하고 있다면(예를 들어서 입력을 기다리는), 이 쓰레드의 다른 고루틴들도 합쳐져서, 실행을 계속 할 것입니다. 자세한 것은 걱정할 필요가 없습니다.

• 다음 프로그램은 Hello from main goroutine을 출력할 것입니다. 어쩌면 Hello from main goroutine 도 출력시킬 것입니다. 어떤 고루틴이 먼저 끝나냐에 따라 달렸습니다.

1. Threads of Execution 8

• 다음 프로그램은 아마, 둘다 출력할 것입니다.

• 순서는 상관없이요

• 다른 가능성은 두번째 고루틴이 매우 느려서 프로그램이 종료전에 어떠한 메시지도 출력안함

1. Threads of Execution 9

• 좀 더 현실적인 예제로 가볼까요?,

1. Threads of Execution 10

• 일반적으로 쓰레드들이 잠자면서 서로를 기다리기 위해 정렬되는 것은 가능하지가 않습니다.

• 다음 절에서는 Go의 동기화 메커니즘 채널을 소개하며 어떻게 Go루틴이 다른 것을 기다리는지 설명해보겠습니다.

2. Channels 11

2. Channels 12

• 채널은 Go언어의 하나의 특징으로, 두 개의 Go루틴이 동기화되어서 실행되고 특정 요소 타입의 값을 전달하여 통신하는 메커니즘입니다.

• <- 연산자가 채널의 방향을 수신(<-chan)인지 송신(chan<-)인지 정하며, 방향이 없으면 양방향입니다

2. Channels 13

• 채널은 레퍼런스 타입이고 make로 할당됩니다.

• 만약 채널이 버퍼되지 않았으면, 송신자는 수신자가 값을 받을 때까지

블록됩니다. 채널이 버퍼되어있으면, 송신자는 값이 버퍼에 복사될때까지만 블록됩니다. 버퍼가 꽉차면 수신자가 값을 받을 때까지 기다린다는 것을 의미합니다. 수신자는 받을 데이터가 있을 때까지 블록됩니다.

2. Channels 14

• close함수는 더 이상의 데이터가 채널로 통해서 전송되지 않는다고 기록하는 것입니다. Close를 호출한 이후, 이전에 전송한 값들이 수신된 이후, 수신동작은 블록킹없이 제로값을 리턴합니다.

• 다중리턴을 받는 것은 추가적으로, 값이 전송되었는지 여부를 가리키는 boolean 값을 반환합니다.

2. Channels 15

• for 구문과 range구문은 채널이 닫힐 때까지, 채널에서 성공적으로 전송된 값을 읽어들입니다.

3. Synchronization 16

• 다음 예제에서는 Publish 함수가 채널을 리턴하게 하여서, text가 발행될 때 이 메시지를 브로드캐스트하게 해봅니다.

3. Synchronization 17

• 우리가 비어있는 구조체 struct{} 를 이용하는 것을 명심합시다. 이것은 명백하게 채널이 사용되고 데이터를 전달하는데 사용되지 않고 오직 신호를 위해 사용되는 것을 말합니다.

• 이것은 어떻게 함수를 사용하는지 보여줍니다. 다음은 결과

4. Deadlock 18

4. Deadlock 19

• Publish 함수에서 버그를 만들어봅시다.

• main프로그램은 다음과 같이 시작합니다. 첫번째 줄을 출력하고 2초동안 기다립니다. 이 시점에서 Publish함수에 의해 시작되는 고루틴은 뉴스속보를 출력하고, 메인고루틴을 waiting상태로 두면서 종료됩니다.

4. Deadlock 20

• 프로그램은 이 시점에서 어떠한 진행을 할 수가 없습니다. 이 조건이 데드락으로 알려져있습니다.

• 데드락은 쓰레드가 서로를 기다리며, 어떠한 것도 진행될 수가 없는 상황입니다.

• Go는 런타임에서 데드락을 감지할 수 있도록 지원되어져있습니다. 어떠한 고루틴도 진행을 할수없는 상황에서, Go프로그램은 종종 세부적인 에러메시지를 제공할 것입니다. 여기에 에러메시지 예제가 있습니다.

• 대부분의 경우 무엇이 Go프로그램에서 데드락을 발생시켰는지, 아는 것은 쉽고, 버그를 고치는 일만 하면 될 것입니다.

5. Data races 21

• 데드락은 안 좋게 들리지만, 동시성 프로그래밍에서 피해가 큰 에러는 데이터 레이스입니다. 이것은 매우 흔하고, 디버그 하기가 힘듭니다.

• 데이터레이스는 두개의 쓰레드가 하나의 같은 변수에 동시적으로 접근하고, 적어도 하나의 접근이 쓰기일 때 발생합니다.

• 이 함수는 데이터레이스를 가지고 있고 행동이 정의되지가 않았습니다. 아마, 1을 출력할 것입니다. 어떻게 이것이 일어나는지 알기 위해 코드 뒤에 한가지 해석을 적어보겠습니다.

5. Data races 22

• 두개의 고루틴 g1, g2가 경쟁에 참여합니다. 어떤 순서로 작업이 이뤄지는지는 알 방법이 없습니다. 다음은 많은 결과중 가능성이 있는 하나입니다.

• g1 이 n으로 부터 0을 읽습니다.

• g2가 n으로 부터 0을 읽습니다.

• g1 이 0에서 1로 값을 증가시키고, g1이 1을 n에 씁니다.

• g2가 0에서 1로 값을 증가시키고, g1이 1을 n에 씁니다.

• 프로그램이 이제 n값을 1로 출력시킵니다.

5. Data races - 역주 첨가 23

• 예제에서 나온 소스가 계속 동작시켜봐도 저자가 의도한 값이 아닌, 정상적으로 2가 나와서 제가, 조금 에러를 낼 수 있는 조건을 넣어봤습니다.

• runtime.GOMAXPROCS로 멀티코어로 돌리고 반복문을 100번 돌려서 같은 변수에 접근하게 해줬습니다. WaitGroup 대기그룹을 만들어서 GO루틴이 실행될때마다, 대기그룹에 숫자를 늘리고, Done()처리를 하여서 Go루틴들을 기다릴 수 있게 하였습니다.

5. Data races 24

• 데이터레이스라는 이름은 다소 오해가 있습니다. 작업의 순서가 정해지지 않은 것뿐만 아니라, 무엇이든지 보장되지 않습니다.

• 컴파일러와 하드웨어는 더 나은 성능을 위해 코드를 자주 들들 볶습니다?(upside-down, inside-out)

• 만약 당신이 쓰레드를 mid-action(내부?) 에서 본다면, 무엇이든지 간에 볼 수 있습니다 ::(예시사진)

5. Data races 25

• 데이터레이스를 피하는 유일한 방법은 쓰레드들간에 공유되는 모든 변경가능한 데이터들에 대한 접근을 동기화시키는 것입니다. 이것을 하기 위한 몇가지 방법이 있는데, GO에서는 보통 채널이나 락을 사용합니다. (로우레벨 메커니즘또한 sync, sync/atomic 패키지에서 가능합니다만 이것은 여기서 다루지 않습니다.)

• GO에서 동시성 데이터를 다루는 선호되는 방법은 채널을 사용하여 실제 데이터를 하나의 고루틴에서 다음 고루틴으로 전달하는 것입니다. 모토는 “공유메모리로 통신하지말고, 통신을 통해 메모리를 공유하라” 입니다.

5. Data races 26

• 이 코드에서는 채널은 두 가지 일을 합니다. 데이터를 하나의 고루틴에서 다른 고루틴으로 전달하고, 동기화의 지점으로 행동합니다 : 값을 보내는 고루틴은 다른 고루틴이 데이터를 받는 것을 기다리고, 값을 받는 고루틴은 다른 고루틴이 데이터를 보내는 것을 기다립니다.

• Go 메모리 모델(링크) : 변수를 하나의 고루틴에서 읽고, 다른 고루틴에서 같은 변수에 쓰는 것을 알고 있게 하는 것은(뭐 이런 뜻인듯?) 꽤 복잡합니다. 하지만 당신이 변경가능한 데이터를 채널-고루틴들을 통해 공유하면 데이터레이스로부터 안전할 것입니다.

6. Mutual exclusion lock 27

• 때때로 데이터 접근을 동기화시키는 것이 채널을 사용하는 것보다 명시적으로 락을 거는 것이 더 편할 때가 있습니다. Go의 표준 라이브러리는 상호배제락을 제공합니다. sync.Mutex가 이런 경우를 위해 존재합니다.

• 이러한 락킹 타입이 동작하기 위해 모든 공유 데이터로의 접근이(읽기,쓰기 모두) 고루틴이 락을 유지할때 수행되는 것이 중요합니다. 하나의 단일 고루틴에서의 실수도 데이터레이스를 유발하면서 프로그램을 망가뜨리기 충분합니다.

• 이러한 점에서 깔끔한 API를 가진 커스텀 데이터구조를 고려해보고, 모모든 동기화가 내부적으로 진행되도록 해야합니다. 이 예제에서는 안전하고 사용하기 쉬운 데이터 구조 AtomicInt를 만들어 하나의 정수를 저장하겠습니다.

6. Mutual exclusion lock 28

• 고루틴이 몇개건지간에, 안전하게 Add와 Value메소드를 통해 이 숫자에 접근할 수 있습니다.

이 부분입니다^^;

7. Detecting data races 29

• Race는 때때로 감지하기 어렵습니다. 이 함수는 data race를 가지고 프로그램이 실행될 때 55555를 출력합니다. 실행해보면, 다른 값을 얻을 지도 모릅니다. (sync.WaitGroup은 Go표준 라이브러리의 일부로, 고루틴 모음이 끝나길 기다립니다.)

• 55555출력에 대해서 i++를 실행하는 고루틴이 각각의 fmt.Println 출력이 실행되기 전에 5번 실행된다는 것입니다. 이러한 사실로 인해, 수정된 값 i 가 다른 고루틴에게 일치되게 보여지는 것입니다.

7. Detecting data races 30

• 간단한 해결책은 지역변수를 사용하여서 고루틴을 시작할때 파라미터넘버로 넘겨주는 것입니다. 이 코드는 올바르며, 예상한 결과를 출력합니다(24031같은..) 고루틴간의 실행순서가 명시되지 않으며 다양하게 될 수 있단 걸 기억합시다.

• 이것 또한 클로져를 사용하면서 데이터 레이스를 피할 수가 있습니다만, 우리는 각각의 고루틴마다 유니크한 변수를 사용하는 것에 주의해야합니다.

7. Detecting data races 31

• 자동적으로 데이터 레이스 감지

• 일반적으로 모든 데이터 경합을 자동으로 감지하기는 가능하지는 않지만, Go에서는 1.1버젼부터 강력한 data race detector를 가지고 있습니다.

• 사용하기 간단합니다. 단순히 -race 플래그를 go 커맨드에 추가하면, 프로그램을 실행하면서 다음의 명확한 정보가 있는 출력을 내줄 것입니다.

• 이 도구는 data race를 발견합니다. 이 race는 20라인의 하나의 고루틴에서 변수에의 쓰기와, 뒤이어서 다른 고루틴에서 같은 변수에 대한 비동기적읽기(22라인) 로 구성됩니다.

• race감지는 오직 실제로 실행되는 동안에 발견한다는 것을 기억해주세요.

8. Select statement 32

• select 구문은 Go동시성 도구의 마지막 도구입니다. 이것은 처리하게될 가능한 통신방법을 고릅니다. (아 말이 어렵다-_-쩝)

• 만약 한 개라도 처리될 수 있는 case가 있다면 그 것들의 일부가 무작위로 골라져서 그에 맞는 statements들이 실행되고, 어떠한 것도 case가 해당되지 않는다면, case중의 하나가 완료될때까지 구문이 블록됩니다.

• 여기에 장난감 예제가 있는데, 어떻게 select구문이 랜덤숫자생성구현을 하는데 사용되는지 보여줍니다.

8. Select statement 33

• 다소 실제적으로, 여기에 어떻게 셀렉트 문이 작업 중 시간제한으로 사용될 수 있는지 보여줍니다. 이 코드는 첫 번째 것이 먼저 처리되는 두 가지 수신구문에서 뉴스를 보여주거나, 타임아웃메시지를 보여줍니다.

• time.After 함수는 Go의 표준라이브러리의 일부입니다. 이것은 특정 시간동안 기다리고 반환채널에 현재 시간을 전송합니다.

9. The mother of all concurrency examples

34

• 시간을 들여서 이 예제를 주의깊게 봅시다. 이것을 완벽히 이해하면, 당신을 Go에서 동시성이 어떻게 동작하는지 알게 될 것입니다. 이 프로그램은 GO루틴이 몇 개이든지 간에 어떻게 채널이 송수신에 사용되는지 보여줍니다.

• 이것은 또한 어떻게 select문이 몇몇의 통신(case)중에서 하나를 고르는지 보여줍니다.

9. The mother of all concurrency examples

35

• 이 부분은 블로그 저자도 별 설명이 없습니다^^;

10. Parallel computation 36

• 동시성 프로그램은 큰 계산처리작업을 분리된 CPU에서 동시적으로 처리하기로 계획된 작업유닛으로 나눕니다.

• 몇 개의 CPU에서의 분산컴퓨팅은 과학이라기보다는 예술(기술?)입니다. 여기 몇가지 규칙이 있습니다.

10. Parallel computation 37

• 각각의 작업 유닛은 계산하기 위해 100us에서 1ms를 가집니다. 만약 유닛이 너무 작으면 문제를 나누는 관리상의 오버헤드와 하위 프로그램 스케쥴링이 너무 커집니다. 만약 유닛이 너무 크다면 전체적인 계산이 단일의 느린 작업 아이템들이 마쳐질때까지 기다려야 할지도 모릅니다. 이러한 성능저하가 스케쥴링, 다른 프로세스들에 의한 인터럽트같은 것들, 안 좋은 메모리 레이아웃으로 인해 많은 이유로 일어날 수 있습니다. (워크유닛의 개수는 CPU의 개수에 독립적이란 것을 기억합시다)

• 데이터공유량을 최소화하는 것입니다. 특별히 고루틴이 분리된 CPU에서 쓰기 작업을 할 때, 동시적 쓰기는 매우 비용이 큽니다. 읽기를 위한 데이터 공유는 덜 중요한 문제입니다.

• 데이터 접근을 위한 좋은 장소를 찾아야 합니다(?) 만약 데이터가 공유메모리에 보관되면, 데이터 불러오기와 저장이 매우 빨라집니다. 다시 한번 이것은 쓰기에서 중요합니다.

10. Parallel computation 38

• 이 예제는 어떻게 비용이 비싼 작업을 나누고, 사용가능한 CPU에 나누는지 나타냅니다. 이것이 우리가 최적화하기 원하는 코드입니다.

10. Parallel computation 39

• 아이디어는 간단합니다. 적절한 사이즈의 작업 유닛을 확인하고 각각의 작업을 각각의 고루틴에서 실행하면 됩니다. 여기 Colvolve의 동시적 버전이 있습니다.

10. Parallel computation 40

• 작업유닛이 정해지면, 스케쥴링은 런타임과 OS시스템에 맡기는 것이 제일 나을 때가 많습니다. 하지만 Go 1.*버전에서는 런타임에게 얼마나 많은 고루틴이 동시적으로 실행될지 말해줘야 합니다. (역주 : 1.5 버전부터는 아마 자동으로 최대가용 한 CPU 개수에 맞춰서 멀티코어로 돌아감)

다시 한번 원 글 출처 CCA3.0 41

• http://www.nada.kth.se/~snilsson/concurrency/

42

THANK YOU ! 즐거운 개발됩시다. 감사합니다.

아라한사 올림

arahansa ------

------

페북 : https://fb.com/me.adunhansa 트위터 : https://twitter.com/arahansa 블로그: http://adunhansa.tistory.com/ 사이트 : http://arahansa.com