Tuesday, 29 November 2011

Google Maps goes indoors By Slashgear.com



Though the final frontier may be space, and according to some the ocean of water that surrounds us here on Earth, Google has decided that it’s inside buildings – and that’s exactly where it’s going with it all-encompassing Google Maps environment. You’ll be able to work indoors with Google Maps 6.0 for Android, for example, using “My Location”, which will soon show you as a blue dot on a map of the location you’re at, including both outdoor AND indoors. No longer will you have to act as a car to get to where you need to be should you simply have a pair of shoes. Nothing but fun ahead!
Google Maps new indoor features will help you get around two of the most labyrinthian locations in your everyday life: airports and malls. One of the first malls to be mapped will be one of the largest in the world, the one not 20 minutes away from your humble narrator’s abode: the Mall of America. You can back out and see the whole mall, or move in close to see your nearby surroundings including current stores, exits, and most importantly, bathrooms. You can choose which floor your own, where you want to go, and switch over to your car with only a couple of taps should you want to head to the nearby IKEA across the street.
You can take a peek at a couple of demonstration videos here, first on the airport feature, then one on the rest of the Google Maps Indoors push as a whole – enjoy the animations! The first locations to participate are first the Mall of America along with several chains like IKEA, Home Depot, a handful of Daimaru, Taskashimaya, and Mitsukoshi stores, and select Macy’s and Bloomingdale’s locations. As far as airports go, currently you’ll be able to explore Hartsfield-Jackson Atlanta International Airport (ATL), Chicago O’Hare (ORD), San Francisco International Airport (SFO) and Narita International (NRT). We’re hoping for Minneapolis International Airport (MSP) to come on soon since the Mall of America is literally 10 minutes away – how about the full experience, Google?
Then for those of you who own businesses and want to get in on the fun by submitting your own floor plans, head over to maps.google.com/floorplans and join in! There’s nothing we love more than Google getting on every street, every city, every business, and someday every home! Map the entire planet!
UPDATE: I stand corrected! Google has informed your humble narrator that there is indeed a brand new look for Minneapolis International in this version of Google Maps, complete with indoor maps galore! Have a peek at the hands-on with this map here:

Microsoft releases Windows Phone 7 demo for iPhone and Android users


If you’ve been curious about Microsoft’s Windows Phone 7.5 Mango platform, you can now try it out directly on your iPhone or Android device without having to download or install anything. Microsoft launched an HTML5 website that simulates the Windows Phone 7.5 Mango interface, allowing you to take a test drive by simply navigating to the site via your mobile browser.
The demo is located at the webpage http://aka.ms/wpdemo and is completely self contained using pre-populated user information such as contact list, agenda, and photos instead of any data on your phone. Microsoft also provides two guidance icons in the form of blue dots to help you learn how to navigate through the interface, which is fairly intuitive and slick already.
This is a nice little move by Microsoft to engage potential users that may be hesitant to switch to a Windows Phone without having first tried out the interface, which is very different from what they might be used to with the iPhone or Android. Microsoft has really stepped up Windows Phone marketing this time around and with a good push from Nokia’s Lumia 800 launch in Europe, recent metrics indicate that the platform may finally be picking up some steam.
[via 9to5 Mac]

Go 언어 By 마소



여러분이 C/C++ 혹은 자바에 경험이 있다면 Go언어는 최근에 나온 다른 개발언어들보다 빨리 익힐 수 있을 것이다.
Go언어는 C언어 계열이다
Go언어는 공식적으로 C언어 계열이다. C언어를 만들었던 켄 톰슨이 Go언어의 핵심 개발자이며 현재 가장 널리 사용되는 언어가 C언어이므로 C언어 계열이라는 것은 큰 장점이다. C언어를 버리고 새로운 형태의 언어를 만드는 것은 어렵기도 하고 대중에게 빠른 시간내에 퍼지기도 어려울 것이다. 따라서 C/C++ 혹은 자바에 경험이 있는 개발자라면 Go언어를 익히는 데 많은 노력이 들지는 않을 것이라 생각한다. C언어에서 Go언어로 오면서 변형된 문법이나 추가된 기능만 익힌다면 쉽게 사용할 수 있다.

이번 호에서는 이 두 언어의 두드러진 차이점을 한 눈에 살펴본 후에 Go언어의 특징을 직관적인 것, 제거된 것, 변경된 것, 추가된 것, 엄격한 것으로 나눠서 알아보자. 마지막으로는 Go언어 testing 패키지를 이용한 단위 테스트도 소개하겠다.
한 눈에 비교하기
Go언어와 C++ 그리고 자바에서 지원하는 기능을 열거하고 각 언어에서 해당 기능을 지원하는지의 여부를 표로 만들었다(<표 1> 참조). 물론 세부적으로 들어가면 비교할 것이 많겠지만 Go언에서 특징이 부각되도록 기능들을 열거했다. 3분 정도 표만 보고 Go언어의 특징을 음미해 보자.
<표 1> 각 개발언어와 기능지원 여부
이 표만 보고 Go언어에 대해 드는 느낌은 무엇인가? 아마 여러 가지 의문이 들었을 것이라 예상된다. 자, 그렇다면 함께 <표 1>에서 나타난 Go의 특징을 하나씩 살펴보자. 우선 Go는 클래스(Class)를 지원하지 않는다. 따라서 상속(inheritance)이나 오버로딩(overloading)도 지원하지 않는다. 그리고 가비지 콜렉터(garbage collector)를 지원한다. 기존 시스템 프로그래밍 언어인 C나 C++에서는 개발자가 할당받은 메모리에 대해 직접 해제해야 하는데 Go언어에서는 자바와 같이 개발자가 메모리의 해제에 대해 신경 쓸 필요가 없다.

다음으로 Go언어의 특징은 포인터(Pointer)는 있는데 포인터 연산이 없다는 것이다. 반면에 C나 C++에서는 포인터와 포인터 연산이 모두 가능하다. 다른 모듈이나 패키지를 사용할 때는 자바와 비슷한 방법으로 구현하며, 자바의 인터페이스(interface)와 유사하게 동일한 키워드로 인터페이스를 지원한다. 다른 두 언어에서 널(null) 값으로 사용하는 것을 Go언어에서는 ‘nil’로 표현한다는 차이가 있다.

그리고 마지막으로 Go언어는 타입변환(type conversion)에 대해 아주 엄격하다. 다른 두 언어에서는 경고(warning) 정도로 넘어가는 것이 Go언어에서는 컴파일 에러(compile error)를 발생시킨다. 이렇게 <표 1>을 통해 Go언어의 특징을 알아봤다. 이제 Go언어의 특징에 대해 감이 잡히는가? 설명이 부족했다면 하나씩 좀 더 깊이 알아보자.
Go는 객체지향언어?

클래스가 없다. 상속도 없다. 당연히 오버로딩도 지원하지 않는다. 그렇다면 Go언어는 객체지향언어일까? “그렇기도 하고 아니기도 하다”고 Go언어를 만든 사람들은 말한다. 타입(type)과 메소드(method)를 갖고 객체지향 스타일의 프로그래밍이 가능하지만 상속은 지원하지 않고 인터페이스(interface)라는 개념으로 다른 접근방식을 지원한다. 임베드 타입(embed type)을 이용하면 서브클래스(subclass)와 유사한 기능을 제공할 수 있다. 따라서 완벽하게 기존의 객체지향을 지원하지는 않지만 비슷한 효과를 낼 수는 있다.
가비지 콜렉션

시스템 프로그래밍에서 가장 많은 코딩 부분을 차지하는 것 중에 하나가 메모리 관리(memory management)다. 버그가 가장 많이 발생하는 부분이기도 하고 프로그래머가 가장 많은 시간을 들이면서 작업하는 부분이기도 하다. 따라서 개발자의 노력을 줄이기 위해서도 가비지 콜렉션이 필요하다. 이미 자바에서는 가상 머신을 지원하고 있지만 Go언어처럼 독립적으로 실행 가능한 파일을 생성하는 경우 가비지 콜렉션을 지원하는 것은 쉽지 않은 일이다. 시스템 프로그래밍 코드가 간결하고 안정적으로 동시성(Concurrency)를 지원하는 코드를 쉽게 작성하기 위해 Go언어에서는 가비지 콜렉션을 지원한다.
포인터 연산이 없다?

포인터는 있는데 왜 포인터 연산을 지원하지 않을까? C개발자라면 느끼겠지만 심각한 버그의 상당부분은 포인터 연산에서 기인한다. 따라서 안전성을 위해 포인터 연산을 지원하지 않는다. 잘못된 주소에 접근해 값을 갖고 오기도 하고 잘못된 주소에 값을 쓰기도 한다. 이처럼 잘못된 주소로 갈 수 있는 포인트 연산을 Go언어에서는 지원하지 않으므로 안전한 프로그래밍이 가능하다.
직관적인 것
Go언어를 처음 접한 사람에게는 생소하지만 직관적인 변수 선언 부분을 살펴보자.

C나 자바 개발자가 Go언어를 처음 접할 때 가장 거부감을 느끼는 부분이 변수 선언부다. Go언어에서는 변수 선업부가 더 길고 기존에 알던 순서와 반대다. 필자의 주변 개발자들도 이런 불평을 하는 경우를 봤다. “뭐야 C언어 계열이라고 하면서 가장 기본적인 변수선언도 다르고 예전보다 더 복잡해진 것 같잖아”라고. 필자도 처음에는 왜 이렇게 바뀌었는지 이해하기가 어려웠다. 초기에 자료도 부족했고 마땅히 설명할 수 있는 논리가 없어서 답답했던 기억이 난다. 이후 Go언어 관련 발표에서 Go언어 개발자가 직접 이렇게 변수를 선언하는 이유를 설명했다. 비로소 “아하! 그래서 이렇게 생겼구나” 이해할 수 있었다. 지금부터 그 이유를 알아보자. 
<리스트 1> C에서 변수 선언
int a;
int b[10];
<리스트 2> Go에서 변수 선언
var a int
var b [10]int

C나 자바에서의 변수 선언은 <리스트 1>과 같다. 우리에게 이미 익숙한 표현이다. <리스트 2>의 Go언어에서의 변수선언을 보자. 앞에 var가 하나 더 붙어 있고 C언어에서의 선언 순서와 반대다. 왜 이렇게 복잡하게 표현했을까? Go언어를 만든 개발자가 밝히는 변수 선언 문법의 비밀은 마치 영어 문장으로 쓰는 것처럼 표현하고자 했다는 것이다. 즉, “변수 a는 정수형이다.”를 영어 문장으로 표현한다면 “Variable a is integer”가 된다.

기존 방식보다 직관적으로 느껴지는가? 우리는 영어를 모국어로 하지 않기에 직관적인 느낌을 얻기보다는 이전에 친근했던 것과 다른 것에 거부감이 들지도 모르겠다. 하지만 <리스트 3>과 <리스트 4>와 같은 경우를 보면 좀더 Go언어의 표현이 직관적이라고 생각할 수 있을 것이다. C언어로 변수를 선언한 <리스트 3>을 보자. c와 d는 같은 타입일까? 코드를 읽거나 작성할 때 오류를 내기 쉬운 경우다. <리스트 4>의 Go언어에서는 b, c가 동일하게 정수형 포인터로 선언된다.
<리스트 3> C에서 변수 선언
int* c, d;
<리스트 4> Go에서 변수 선언
var c, d *int

변수 선언시 <리스트 5>와 같이 다양한 방법으로 코드 타이핑 양을 줄일 수도 있다.
<리스트 5> Go에서 다양한 변수 선언 방법
// var를 사용해 그룹으로 묶기
var(
   a int
   f float64
   s string
)
// 선언시 값을 할당하는 경우 type 생략 가능
var i = 4
// 함수내에서만 사용되며 ‘:=’로서 var, type 생략 가능
i := 1

Go언어를 시작할 때 혼란을 겪는 첫 번째 장애물이 바로 변수 선언이다. 기존 방법에서 변경된 이유와 사용법을 알면 Go언어로 된 소스를 읽기가 한결 쉬울 것이다. Go언어에 적용된 문법들은 직관적이며 코드 타이핑 양을 줄이는 방향으로 변화했다는 것을 기억하고 이 글을 읽는다면 도움이 될 것이다.
제거된 것
이제 Go언어에서 제거된 것들을 살펴보자.
세미콜론
<리스트 6> 세미콜론이 생략된 선언
var i int

C뿐만 아니라 자바에서도 스테이트먼트(statement)가 끝날 때 세미콜론(‘;’)을 넣는다. <리스트 6>와 같이 Go언어에서는 세미콜론을 사용하지 않고 변수를 선언했다. 실제로 Go언어에서는 세미콜론을 사용할 일이 거의 없다. Go언어가 제공하는 패키지 소스를 봐도 for나 if문에서의 사용을 제외하고는 거의 찾아보기가 힘들다.

기존 언어에서 세미콜론은 해당 스테이트먼트가 종료된다는 것을 의미한다. Go언어에서는 세미콜론을 제거했다기보다는 생략할 수 있다는 것이 정확한 표현이다. 따라서 세미콜론을 넣어줘도 아무런 문제가 되지 않는다. 컴파일시 컴파일러가 자동으로 ‘;’를 삽입하기 때문이다. Go언어에서도 여러 스테이트먼트를 하나의 라인에 표현할 때에 ‘;’를 사용할 수 있다.
<리스트 7> Go에서 if문 사용시 주의할 사항
if i == 0 {  //올바른 사용
   f()
}
if i == 0    // 컴파일 에러. ‘;’이 삽입돼 if i == 0 ;이 되기 때문이다.
{          
   f()
}
// 아래와 같은 오류 발생
prog.go:19: missing condition in if statement
prog.go:19: i == 0 not used

컴파일러가 자동으로 세미콜론을 삽입하므로 <리스트 7>에서 보는 바와 같이 주의해서 사용해야 한다. if문이나 for문과 같이 ‘{...}’를 사용하며 ‘{’를 같은 라인에 붙여서 사용한다. C 개발자가 자주 범하기 쉬운 오류다. 또 C언어에서는 if나 for를 사용하는 경우 ‘{}’를 생략할 수 있는 경우도 있지만 Go언어에서는 반드시 ‘{}’를 사용해야만 한다.

C언어에서는 ‘if (condition)’에서 ‘()’를 사용하지만 Go에서는 <리스트 7>에서와 같이 생략이 가능하다.
while
<리스트 8> C에서 whlie문
while(condition)
{
   //....}
<리스트 9> Go에서 for로 while 구현 방법
for condition{
   //....}

<리스트 8>과 같이 C나 자바에서는 while 키워드를 사용해 반복제어문을 구현할 수 있다. for와 유사한 동작을 한다. C언어를 처음 배울 때 ‘while과 for의 차이점은 대체 무엇일까?’라는 의문을 한 번쯤 가져봤을 것이다. 어떤 경우에 for를 쓰고, while을 쓰는 경우는 또 어떤 경우일까? 실제로 구글에서 ‘for while 차이점’으로 검색해 보면 많은 답변을 찾을 수 있다. 동일한 기능을 구현할 수 있으며, 코드 가독성이나 컴파일러마다 성능 차이를 낼 수 있다는 정도로 판단할 수 있다. Go언어에는 ‘while’이라는 키워드가 없다. <리스트 9>와 같이 for를 사용해서 while을 구현한다. for 이후에 조건이 오면 while과 동일하다. 사용방식만 다르고 동일한 기능을 수행하던 것을 하나로 묶었으며 마치 중복된 코드에 리팩터링을 해서 중복을 제거한 느낌이 든다.
접근 제한자 - public, private
<리스트 10> 자바에서 접근 제한자
Class A{
   public void f(){
   
   }
   private void g(){
   
   }
}
<리스트 11> Go에서 접근 제한 방법
func F(){
}
func g(){
}

지금까지 본 바로도 Go언어에서는 불필요한 키워드를 최대한 줄이려고 노력했다는 것을 알 수 있다. 그 중에 가장 재밌다고 생각되는 표현 중에 하나는 자바에서 사용하는 public, private과 유사한 기능에 대한 문법이다. 모듈 내부에서 사용하는 경우에는 소문자로, 외부에서 접근할 수 있도록 하기 위해서는 대문자로 시작하면 된다.

자바에서 클래스 외부에서 접근을 허용하는 경우 <리스트 10>과 같이 ‘public’이라는 키워드를 사용한다. 그리고 클래스 내부로 제한하는 경우에는 ‘private’이라는 키워드를 사용한다. <리스트 11>에서와 같이 Go언어에서는 함수의 첫 글자를 ‘F’와 같이 대문자로 시작하는 경우 외부 모듈에서 접근 가능한 public이 되고 ‘g’와 같이 소문자로 시작하는 경우 외부 모듈에서 접근할 수 없게 된다. 이렇게 함으로써 키워드 두 개(public, private)를 절약하는 효과가 있다.
초기화
<리스트 12> Go에서의 사용하는 zero values
numeric : 0
boolean : false
string : ""
pointer, map, slice, channel : nil
struct : zeroed struct

C나 자바로 개발하는 경우 함수나 메소드 내에서 변수를 선언하고 개발자가 의도한 값으로 초기화를 수행한다. 실제 코딩을 하다보면 대개의 경우 묵시적으로 특정값을 사용해서 초기화시킨다. Go언어에서는 변수를 선언하면 자동으로 미리 정의한 <리스트 12>의 ‘zero value’로 초기화한다. 따라서 개발자가 초기화 코드를 따로 넣을 필요가 없다. Go언어에서 정의한 ‘zero value’는 C나 자바로 개발할 때 묵시적으로 자주 사용하던 값과 일치하는 것을 알 수 있다. 따라서 기억하는 데 크게 노력이 들지 않는다. <리스트 12>는 각 타입에 대한 zero value다.
변경된 것
이번에는 변경된 사항들을 짚어보자.
인터페이스

자바에서 제공하는 인터페이스와 유사하다. 하지만 사용하는 방법이 다르다. Go언어를 개발한 롭 파이크(Rob Pike)는 자바의 인터페이스보다 ‘참신(novel)’하다고 표현한 바 있다.

인터페이스의 정의는 ‘메소드들의 집합’이다. 이런 정의들만으로는 감이 오지 않을 것이다. 필자가 Go언어의 인터페이스를 이해하기 위해 했던 방법은 자바의 인터페이스로 구현한 것을 동일하게 Go언어로 구현해 보는 것이다. <리스트 13>부터 <리스트 20>을 통해 인터페이스에 대해 감을 잡을 수 있을 것이다.
<리스트 13> 자바에서 Drawer 인터페이스 정의
interface Drawer{
   public void Draw();
}
<리스트 14> Go언어에서 Drawer 인터페이스 정의
type Drawer interface{
   Draw()
}
<리스트 15> 자바에서 Circle 클래스 구현
class Circle implements Drawer{
   int r;
   Circle(int r){
      this.r = r;
   }
   public void Draw(){
      System.out.println("Circle is Draw : "+ r);
   }
}
<리스트 16> Go언어에서 Circle 구조체 및 메소드 구현
type Circle struct{
   r int
}
func (c Circle) Draw(){
   fmt.Println("Circle is Draw : ", c.r)
}
<리스트 17> 자바에서 Rectangle 클래스 구현
class Rectangle implements Drawer{
   int w, h;
   Rectangle(int w, int h){
      this.w = w;
      this.h= h;
   }
   public void Draw(){
      System.out.println("Rectangle is w="+w+" h="+ h);
   }
}
<리스트 18> Go언어에서 Rectangle 구조체 및 메소드 구현
type Rectangle struct{
   w, h int
}
func (r Rectangle) Draw(){
   fmt.Println("Rectangle is w=", r.w, " h=", r.h)
}
<리스트 19> 자바에서 인터페이스 사용
public class MyMain {
   public static  void DrawForm(Drawer d){
      d.Draw();
   }
   public static void main(String[] args) {
      Circle myC = new Circle(5);
      DrawForm(myC);
      Rectangle myR = new Rectangle(3, 4);
      DrawForm(myR);
   }
}
<리스트 20> Go언어에서 인터페이스 사용
func DrawForm(d Drawer){
   d.Draw()
}
func main() {
   var myC Circle = Circle{5}
   var myR Rectangle = Rectangle{3, 4}
   DrawForm(myC)
   DrawForm(myR)
}
switch

C에서 switch문을 사용할 때 가장 실수하기 쉬운 부분이 break문이다. break를 넣어야 하는데 넣지 않은 경우와 넣지 말아야 하는데 넣은 경우가 문제가 된다. switch 내부에 break문을 쓰거나 생략하는 등 복잡하게 사용해 그 의도가 명확하게 드러나지 않는 경우도 많다. 필자가 경험한 한 개발부서에서는 break를 무조건 넣는 것을 코딩룰(coding rule)로 정하고 있다. Go언어에서는 직관적이며 간결화시키는 방법으로 이 문제에 접근한다. <리스트 21>처럼 switch문에서 break문을 사용하지 않는다. 또 의도를 명확히 하기 위해 <리스트 22>처럼 다른 case지만 동일한 동작을 해야 하는 경우 각 case를 ‘,’로 연결해 표현한다. switch문에 사용하는 조건은 상수뿐만 아니라 비교연산이 가능한 것이라면 어떤 것이라도 가능하다. 따라서 string도 사용가능하다.

switch만 있는 경우에는 기본적으로 true가 된다. 기존에 if/else if/else로 표현했던 것을 다음의 <리스트 23>과 같이 표현할 수 있다.
<리스트 21> Go언어에서 일반적인 switch 사용
switch i {
   case 0:   // i가 0인 경우 하는 일이 없으며 break를 사용하지 않는다.
   case 1:   // i가 1인 경우만 수행한다.
      doSomething()
}
<리스트 22> Go언어에서 같은 동작을 수행하는 case들
switch i {
   case 0, 1:   // i가 0 혹은 1인 경우 수행한다. 의도가 명확히 표현됨.
       doSomething()
}
<리스트 23> Go언어에서 switch 이후 생략된 경우
switch {   // switch만 있는 경우 true를 기본값으로 가진다.
   case i < 0:
      f1()
   case i == 0:
      f2()
   case i > 0:
      f3()
}
함수

<리스트 24>는 C에서, <리스트 25>는 Go언어에서 함수를 사용하는 경우다. 아주 비슷한 형태다. 가장 두드러진 차이점은 Go언어에서 함수를 사용하는 경우 앞에 ‘func’ 키워드를 붙여준다는 것이다. 그리고 반환 타입에 대한 정의가 C에서는 함수 시작부분에 왔지만 Go언어에서는 맨 마지막에 왔다. 실제 이 함수의 동작을 영작해 보면 ‘Function add returns integer’로 표현할 수 있으며 반환 타입이 함수의 끝에 오는 것이 더 직관적일 수 있다. 
<리스트 24> C에서 add 함수
int add(int a, int b){
   return a+b
 }
<리스트 25> Go언어에서 add 함수
func add(a,b int) int{
   return a+b
}

<리스트 26>은 동일한 타입을 갖는 정수형인 i, j, k와 문자열 타입인 s, t로 묶을 수 있다. 이를 통해 코드가 한결 짧아지고 깔끔해졌다. 
<리스트 26>  Go언어에서 동일 함수 인자 묶기
func f(i int, j int, k int, s string, k string)
func f(i, j, k int, s, t string)
<리스트 27> Go언어에서 두 개 이상의 값을 반환
func add(a, b int) (int, bool){
   return a+b, true
}

두 개 이상의 값을 반환할 수 있다. <리스트 27>에서는 정수형, 불린(Boolean)형 이렇게 두 개의 값을 리턴하는 경우를 예로 보여준다.
배열

<리스트 28>은 C의 배열보다는 파스칼(Pascal)에서 사용하는 배열과 유사하다. 여기서는 세 개의 정수형을 갖는 배열 ar을 선언했다. Go언어는 선언시 ‘zero value’로 초기화된다. C에서 사용하는 배열과 가장 구별되는 특징은 포인터가 아니라 값(value)이라는 것이다. 사이즈를 구하기 위해 len()을 사용한다. <리스트 28>의 경우 실행하면 3이 된다.
<리스트 28> Go언어에서 배열선언과 사이즈 구하기
var ar [3]int
len(ar)
<리스트 29> Go언어에서 배열 리터럴
[3]int{1, 2, 3}   // 3개 정수를 갖는 배열
[10]int{1, 2, 3}  // 10개 정수를 갖는 배열 중 처음 3개에 값을 대입
[...]int{1, 2, 3}  // [...]을 가지는 경우 {}안에 있는 엘리먼트의 갯수로 결정된다. 따라서 [3]int 배열이 된다.
추가된 것
Go언어에는 스왑, 슬라이스, Defer가 추가됐다.
스왑
<리스트 30> C에서 스왑
int i, j, temp;
temp = i;
i = j;
j = temp;
<리스트 31> Go에서의 스왑
i, j = j, i

스왑은 두 변수에 들어있는 값을 서로 맞바꾸는 연산이다. 많은 정렬 알고리즘에서 값들의 순서를 바꾸기 위해 사용된다. <리스트 30>은 C나 자바에서의 구현방법이다. 반면에 Go언어에서는 스왑을 <리스트 31>과 같이 표현할 수 있다. 예제에서는 두 개 변수의 스왑을 보여줬지만 물론 두 개 이상의 값들도 동일한 방법으로 스왑이 가능하다.
슬라이스

포인터 연산이 없는 대신 배열(array)의 특정 부분을 참조(reference)하기 위해 슬라이스가 사용된다. 개념적으로 슬라이스는 0번째 엘리먼트(element)를 가리키는 포인터와 슬라이스에 있는 엘리먼트의 개수(length), 담을 수 있는 최대 엘리먼트 허용 개수(capacity) 이렇게 세 부분으로 구성된다. <리스트 32>에서 배열과 슬라이스의 선언방법의 차이를 알 수 있다. 배열에서 사이즈를 지정하는 부분이 없으면 슬라이스가 된다. 실제로 Go언어의 소스를 보다보면 배열보다 슬라이스가 더 많이 보인다.
<리스트 32> C에서의 배열과 슬라이스 선언
var ar [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}   // 배열 ar
var a []int   // 슬라이스 a
<리스트 33> Go에서의 다양한 슬라이스 이용방법
a = ar[7:9] / /  ar의 7, 8번째 값을 참조한다.
len(a) // a의 사이즈는 2가 된다.
a = ar[:n] // ar[0:n] array의 0번째부터 n-1번째까지 참조한다.
a = ar[n:] // ar[n:len(a)] array의 n번째부터 끝까지를 참조한다.
a = ar[:] // ar[0:len(ar)] 즉 array전체를 참조한다.

슬라이스의 경우 메모리를 할당하는 방식이 아니라 참조하는 방식이다. 따라서 비용이 적게 들어 부담 없이 필요할 때마다 사용하면 된다. <리스트 33>은 배열과 슬라이스를 이용하는 방법에 대해 설명한다.
Defer
<리스트 34> C에서의 File IO
int main() {
   FILE *fs;
   char ch;
   fs = fopen("test.txt", "r");
   if (fs == NULL)
      exit();
   while( 1 )
   {
      ch = fgetc(fs);
      if (ch == EOF)
         break;
      else
         doSomething(ch);
   }
       
   fclose ( fs ) ;
}
<리스트 35> Go에서의 File IO
func data(fileName string) string {
   f := os.Open(fileName)
   defer f.Close()
   contents := io.ReadAll(f)
   return contents
}

Go언어에 추가된 키워드 중에 ‘defer’가 있다. 가장 유용할 때가 파일 입출력(File IO) 기능을 구현할 때다. <리스트 34>는 C로 구현된 코드로 파일을 열어서 파일의 끝까지 읽으면서 처리한다. 파일을 다 읽은 후에는 마지막으로 파일을 닫는 fclose(fs)를 호출한다. <리스트 35>은 Go언어로 구현한 비슷한 예제다. 파일을 열기 위해 os.Open()을 하고 바로 f.Close()가 호출된다. f.Close()는 분명히 파일을 읽기 위한 동작인 io.ReadAll()를 마친 뒤에 호출되어야 마땅하다. 이쯤 되면 f.Close()와 함께 사용된 defer가 하는 일을 짐작할 수 있을 것이다.

<리스트 35>에서 data() 함수가 반환되는 시점에 defer 키워드를 사용한 함수가 호출된다. 정리하자면 Open → ReadAll → Close 순으로 호출된다. Open, Close와 같이 열고 닫는 동작이 서로 쌍으로 존재하는 경우 두 함수를 함께 붙여서 사용할 수 있다. 이렇게 함으로써 가독성이 높아지며 잊지 않게 도와주는 효과가 있다. 파일을 닫거나 뮤텍스(mutexes)의 락(lock)을 해제하는 경우에도 유용하게 사용된다.
엄격한 것
마지막으로 Go언어에서 엄격해진 것을 알아보자.
선언 후 사용하지 않는 변수
<리스트 36> Go에서 선언 후 사용하지 않는 변수
func add(i, j int) int{
   result := 0
   return i + j
}
<리스트 37> Go에서의 컴파일 에러 발생
prog.go:5: result declared and not used

처음 Go언어로 프로그래밍을 할 때 가장 짜증나게 혹은 괴롭히는 것 중에 하나를 소개하려고 한다. <리스트 36>에서 result 변수를 선언하고 사용하지 않는 경우 <리스트 37>과 같이 컴파일 에러가 발생한다.

생각나는 변수들을 먼저 선언하고 소스를 작성하는 경우가 많다. 막상 구현을 한 후 해당 변수를 사용하지 않는 경우가 자주 있다. 이럴 때마다 <리스트 37>과 같은 컴파일 오류가 발생한다. 컴파일 단계에서 코드에 불필요한 변수는 반드시 걸러내겠다는 확고한 신념이 느껴진다. 불필요한 변수 선언을 하지 않게 되어 깔끔한 코드를 유지하는데 확실히 효과가 있다. 이런 제약을 잘 이용하면 좋은 프로그래밍 습관을 기를 수도 있다.
암시적 형변환(implicit type conversion)
<리스트 38> 암시적 형변환
var i16 int16
var i32 int32
i32 = i16
<리스트 39> 명시적 형변환
var i16 int16
var i32 int32
i32 = int32(i16)

암시적 형변환은 컴파일러가 자동으로 형변환을 하는 것을 말한다. Go언어는 버그가 발생할 여지를 최소화하는 데 주력하고 있다. 기존 개발언어에서는 컴파일시 경고로 충분했을지 모르지만 Go언어에서는 컴파일시 에러가 발생한다. <리스트 38>과 같이 Go언어에서 암시적 형변환을 시도하는 경우 ‘prog.go:15: cannot use i16 (type int16) as type int32 in assignment’과 같은 컴파일 에러를 보게 된다. <리스트 39>처럼 Go언어에서는 명시적인 형변환을 해야 한다. int16을 int32에 대입할 때도 반드시 명시적인 형변환을 해야만 한다.
<리스트 40> Go에서 타입명이 다르면 명시적인 형변환
type MyInt int
var i int
var j MyInt
func main(){
   i = 3
   //j = i    // 결과는 : cannot use i (type int) as type MyInt in assignment
   j = MyInt(i)   // 반드시 명시적인 형변환이 필요하다.
}

<리스트 40>은 좀 더 극단적인 경우다. MyInt라는 새로운 타입을 정의했다. int와 MyInt가 같다고 생각해 j = i처럼 대입하면 컴파일 에러가 발생한다. 타입명이 일치하지 않으면 반드시 명시적인 형변환이 필요하다.
단위 테스트

단위 테스트(unittest)는 안심하고 개발언어와 라이브러리를 사용할 수 있는 안전망 역할을 한다. 그렇다면 Go언어에서는 어떨까? Go언어는 설치할 때 단위 테스트를 동시에 실행한다. 자신의 개발환경에서 실제 라이브러리들의 실행 결과를 바로 확인할 수 있으므로 안정감을 준다.
<리스트 41> 패턴찾기 문제
aaaaa -> a
ababab -> ab
abaaba -> aba
c.c.c. -> c.
<리스트 42> 테스트 작성 - pattern_test.go
//pattern_test.go
package pattern
import (
   "testing"
)
type patternTest struct{
   in, out string
}
var patternTests = []patternTest{
   patternTest{"aaaaa", "a"},
   patternTest{"ababab", "ab"},
   patternTest{"abaaba", "aba"},
   patternTest{"c.c.c.", "c."},
   patternTest{"abcdefg", "abcdefg"},
}
func TestPatterns(t *testing.T){
   for _, e := range patternTests{
      v := getPattern(e.in)
      if v != e.out {
         t.Errorf("getPattern(%s) = %s, but want %s", e.in, v, e.out)
      }
   }
}
<리스트 43> 구현 코드 작성 - pattern.go
//pattern.go
package pattern
import (
   "strings"
)
func getPattern(in string) string{
   strLen :=len(in)
   for i:=0; i<(strLen/2); i++ {
      pattern := in[0:i+1]
      patternLen := len(pattern)
      result := strings.Repeat(pattern, strLen/patternLen)
      if result == in{
         return pattern
      }
   }
   return in
}

인터넷에 있는 간단한 프로그래밍 문제를 단위 테스트로 풀어봄으로서 Go언어의 단위 테스트를 경험해 보자. 지금까지의 내용을 이해했다면 코드를 읽는 데 큰 어려움은 없으리라 생각한다.

pattern_test.go에서 테스트 코드를 먼저 작성한다. pattern. go에서 실제 문제를 해결하는 코드를 구현한다. 만약 여러분이 C/C++환경에서 단위 테스트를 위한 도구인 CppUTest나 자바의 JUnit를 사용해 본 적이 있다면 비교해 보는 것도 좋겠다.
마치며
아직도 풀어놓지 못한 Go언어의 특징이 많다. 다른 상세한 특징과 예제들은 Go언어 홈페이지(http://golang.org)에서 찾아볼 수 있다. 지금까지의 내용을 이해했다면 제공하는 문서를 읽고 이해할 수 있는 기본기는 갖춰졌으리라 생각한다

Go 언어의 동시성 By 마소



지난 1회에서 현재 Go언어가 가진 약점을 간략히 소개했었다. 상용 소프트웨어에 Go언어를 적용하려는 개발자에게 Go언어의 로드맵이 정확히 제공되지 않아 일부 개발자는 답답함을 토로하기도 한다는 내용이었다. 버전마다 문법이 달라 지난달에 작성한 코드가 현재 버전에서는 컴파일이 안되는 경우도 있었다. 언어가 초기에 발전해 나가는 시점이라 어쩔 수 없지만 언제쯤 상용 혹은 안정적인 버전이 나올까에 대한 언급마저 없는 것은 필자가 봐도 이해하기는 어려운 점이었다. 이러다 보니 참고할 만한 Go관련 책이 나오기도 힘든 상황이다. 이런 사항들은 Go언어를 아끼는 개발자라면 누구나 느끼는 점이다.

이런 문제들은 Go언어 개발팀에 의해 내년 상반기에 공개될 ‘Go version 1’로 해결될 것으로 기대된다. 한번 작성한 코드는 꽤 오랜 시간 동안 수정 없이 컴파일과 실행을 할 수 있게 될 것이다. 물론 1.1 혹은 1.2 등으로 버전업이 되면서 version 1에서 나온 버그들을 수정해 나갈 것이고 일부 기능은 추가될 것으로 전망된다. 이에 맞춰서 내년 상반기에는 다양한 자료와 관련 프로젝트들이 쏟아져 나올 것이라 예상된다. 이제 곧 Go언어의 안정 버전이 나온다고 하니 이 글을 읽는 독자라면 조금 일찍 준비하는 개발자가 돼 보는 것은 어떨까?
병렬성! 동시성?
병렬성은 <그림 1>과 같이 문제를 여러 연산으로 나눠 이를 프로세서나 코어 혹은 분산 환경에서 동시에 실행한다. 다시 말해 실행이 동시에 수행된다는 것을 의미한다.
<그림 1> 병렬성
동시성은 <그림 2>와 같이 여러 연산들이 동시에 수행돼 이 연산들이 서로 상호작용이 발생할 수 있는 시스템의 특성을 말한다. 연산들은 단일 프로세서나 코어에서 시분할(time-sharing)방법으로 동시에 실행되거나 여러 프로세서나 코어 혹은 분산 환경에서 동시에 실행될 수도 있다. 동시성을 가지는 시스템은 앞서 언급했듯이 연산들 사이에 상호작용이 발생할 수 있으며 이때 공유 리소스에 대해서 교착상태(deadlock)나 기아상태(starvation)와 같은 문제가 야기될 수 있다. 프로그래밍 관점에서 추상화된 개념이라고 생각할 수도 있다.
<그림 2> 동시성
뿌리 찾기

1970년대 후반으로 오면서 멀티프로세서가 연구주제로 떠오르기 시작했다. 멀티프로세서 프로그래밍은 운영체제, 인터럽트, 입출력 시스템, 메시지 전달과 연관이 깊고 이를 통해 아래와 같은 개념들이 도입됐다.
 · 세마포어(Semaphores) (Dijkstra, 1965)
 · 모니터(Monitors)(Hoare, 1974)
 · 뮤텍스와 락 (Mutexes and Locks)
 · 메시지 전달(Message passing) (Lauer & Needham 1979)
1978년 영국의 컴퓨터 과학자인 토니 호아(C. A. R. Hoare)가 CACM 논문에서 처음으로 동시성을 지원하는 시스템에서 상호작용 패턴을 표현하는 언어인 CSP(Communicating sequential processes)를 소개했다. CSP에선 동시성을 커뮤니케이션의 입출력으로 본다. 메모리를 공유하는 방식이 아니라 동기화 방식으로 통신된다. 이후 동시성을 지원하는 개발언어에 지대한 영향을 끼쳤고 지금까지도 관련된 많은 연구가 행해지고 있다. 한편, 토니 호아는 프로그래밍 언어에 공헌을 인정받아 1980년에 튜링상(Turing Award)를 수상했었다.
<그림 3> CSP에 영향을 받은 언어들

<그림 3>을 보면 CSP에서 영향을 받은 언어들을 볼 수 있다. 생소한 언어들도 있고 한 번쯤은 들어본 언어들도 있을 것이다. Occam은 1983년에 발표된 언어로 기본 CSP에 가장 가까운 언어로 인모스(Inmos)라는 영국의 반도체 회사에서 만들었다. Erlang은 CSP의 영향을 받은 함수형 언어로 스위칭 소프트웨어에 적용하기 위해 에릭슨(Ericsson)에서 개발했다. Erlang란 이름은 덴마크의 수학자(Agner Krarup Erlang)의 이름을 따서 지었고 1998년에 오픈소스로 공개됐다.

Newsqueak는 C와 유사한 언어로 동시성 지원을 위한 연구를 목적으로 개발됐다. Limbo는 벨 연구소(Bell Labs)에서 만든 언어로 동시성을 지원하는 분산 시스템을 위해 개발됐다. <그림 3>을 보면 News queak과 Limbo 그리고 Go언어를 같은 박스에 넣어뒀다. 3개 언어는 어떤 연관성이 있을까? 모두 Go언어를 만든 사람 중에 한 명인 롭 파이크(Rob Pike)가 개발한 언어로 채널이라는 개념을 도입했다는 공통점이 있다. 롭 파이크는 80년대부터 동시성을 지원하는 언어를 연구하고 개발했기에 Go언어 동시성은 그의 노력의 결정체라고 할 수 있다.
기초 다지기

본격적으로 Go언어의 기초를 다져보자.
goroutine
Go언어는 동시성을 지원하기 위해 자체적인 goroutine을 갖고 있고 함수를 비동기적으로 실행하는데 사용된다. 이해를 돕기 위해 자바와 C언어에서 goroutine괴 유사한 예제를 살펴보자.
doSomething이라는 함수 혹은 메소드로 스레드를 실행하는 방법을 살펴보자.
<리스트 1> 자바에서 스레드 실행하기
class SimpleThread extends Thread{
     public void doSomething(){
          //...
     }
     public void run() {
          doSomething();
     }
}
public class ThreadTest {
     public static void main(String[] args) {
          new SimpleThread().start();
     }
}

<리스트 1>은 자바에서 스레드를 실행시키는 방법이다. 스레드를 상속하고 run 메소드를 오버라이드한다. 실제로 호출하는 쪽에서는 해당 스레드 인스턴스를 생성하고 start 메소드를 호출하면 새로 생성된 스레드로 doSomething 메소드를 실행시킬 수 있다.
<리스트 2> C언어에서 스레드 실행하기
void *doSomething()
{
     //...
}
int main()
{
    pthread_t thread_t;
     if (pthread_create(&thread_t, NULL, doSomething, NULL) < 0)
     {
          perror("thread create error");
          exit(0);
     }
     return 1;
}

<리스트 2>는 C언어에서 pthread를 선언하고 pthread_create를 이용해서 실행시키는 방법이다. pthread_create 함수의 인자로 실행시킬 함수 포인터를 전달한다. 이렇게 하면 새로 생성한 pthread로 doSomething 함수를 실행시킬 수 있다.
<리스트 3> Go언어에서 goroutine 실행하기
func doSomething(){
     //....
}
func main(){
     go doSomething()
}

<리스트 3>은 Go언어에서 goroutine을 실행시킨다. 문법이 아주 간단하다. goroutine으로 실행시킬 함수를 go 다음에 적으면 된다.

goroutine은 다른 언어에서 사용하고 있는 ‘스레드’나 ‘코루틴(coroutine)’과 유사하지만 다른 속성을 가진다. 동일한 주소 공간에서 다른 goroutine들과 함께 동시에 실행되며 스레드에 비해서 가볍고 스택 주소 공간의 할당이 적다.  
<그림 4> C언어 메모리 구조
C언어 프로그램이 실행될 때 주소 영역은 <그림 4>와 같다. 하단부터 보면 코드 영역, static data 영역이 있고 상단에는 스택 영역이 있다. 힙(heap) 메모리가 필요하면 상단으로  힙 메모리가 증가되고 스택 메모리가 더 필요하면 스택 영역이 아래로 증가된다. 기존의 C언어에서 스택은 메모리 블록의 연속이다. 스레드의 생성과 함께 필요한 최대 크기의 메모리 블록을 할당하며 일반적으로 1MB의 스택 영역을 할당받는다. 스레드의 수명이 짧으면 몇 KB만으로 충분하지만 강제로 1MB가 할당돼 32bit 운영체제에서 메모리 공간이 4GB로 제한되므로 4,000개 이상의 pthread를 만드는 것이 불가능하다.

Go언어의 경우 스택을 링크드 리스트(linked-list)로 관리한다. 따라서 할당받은 공간이 충분하지 않으면 추가로 스택을 늘려달라고 요청해야 한다. goroutine은 매번 커널 스레드를 생성하지 않고 <그림 5> 처럼 일부 커널 스레드를 멀티플렉싱하여 스택을 확장한다. 그러므로 C언어에서 스레드를 매번 생성하는 것보다 goroutine이 효율적이고 더 많은 goroutine을 만들 수 있다.
<그림 5> gorutine의 멀티플렉싱 개념
채널

<리스트 3>의 코드에 doSomething 함수에 문자 출력 소스를 추가해고 실행해도 화면에 아무것도 출력되지 않고 프로그램이 종료된다. goroutine의 동작 여부를 확인할 수 없는 상황이다. 실제 goroutine이 생성됐더라도 goroutine을 생성하고 화면에 문자를 출력하기 전에 main 함수가 끝나버린다.

doSomething 함수를 실행하는 goroutine과 main 함수 사이에 통신이 필요하다. 즉 doSomething은 실행이 완료됐음을 보내야하고 doSomething은 완료됐다는 것을 통보 받아야 한다. <리스트 4>는 상호간 통신을 위한 코드가 추가된 소스로 Go언어에서 gorutine 사이의 통신은 ‘채널’을 통해 이뤄진다. 
<리스트 4> 채널로 통신하는 예
func main(){
     c := make(chan int)
     go func(){
          //..
          c <- 1
     }()
     <- c
     fmt.Printf("main End")
}

<그림 6>은 C언어나 자바에서는 흔히 볼 수 있는 스레드간 메모리를 공유하는 방식이다. 공유 메모리에 각 스레드는 I/O가 가능하며 하나의 스레드만 해당 메모리에 접근 하는 경우 ‘lock’을 이용한다. 물론 Go언어에서도 동일한 방식을 지원한다.
<그림 6> thread에서 메모리 공유
Go언어는 CSP 개념을 이용한 채널로 두 개의 goroutine간에 메모리를 공유할 수 있다. 
<그림 7> goroutie A와 goroutie B 사이의 채널
<그림 7>과 같이 두 개의 goroutine 사이는 채널이 존재한다. 개념적으로 메모리를 공유할 수 있는 추상적인 통로가 존재한다고 생각하면 된다.
<그림 8> goroune A, B 사이의 채널을 통해 동기화로 공유 방법
<그림 8>은 두 goroutine이 공유할 데이터가 있는 경우 채널을 통해 상대 goroutine으로 데이터를 전달할 수 있음을 보여준다. goroutine은 채널을 통해 데이터 ‘V’를 goroutine A에서 goroutine B로 전달한다.

채널은 어떤 속성이 있고 또 어떻게 사용하는지 살펴보자. <리스트 5>와 같이 ‘chan’ 키워드를 사용한다. elementType는 데이터 형으로 만약 ‘chan int’ 라고 선언하면 채널이 int형임을 의미하며 struct도 될 수 있다.
<리스트 5> chan 기본 사용법
chan elementType

<리스트 6>처럼 채널 변수에 make 키워드를 할당하면 참조 타입이 되고 chan 변수를 타른 변수에 할당하면 동일한 채널을 공유하게 된다. 이는 두 개의 변수로 상호간 데이터 공유가 가능한 것으로 마지막 예제에서 보다 자세히 살펴보자.
<리스트 6> Go언어에서 make 키워드의 역할
var c = make(chan int)

<리스트 7>은 이미 변수 선언을 다룬 2회회의 ‘:=’를 통해 채널의 선언 및 할당이 이뤄질 수 있음을 말해준다.
<리스트 7> :=을 통한 선언 및 할당
ci := make(chan int) //int형 채널 생성
cs := make(chan string) //string형 채널 생성

채널은 연산자로 ‘<-’가 사용되며 ‘<-’는 단항연산자(unary operator)와 이항연산자(binary operator)의 두 가지 용도를 갖고 있다. 화살표 방향은 데이터의 흐름을 의미하며 직관적으로 이해하기 쉽다. <리스트 8>은 채널 c ‘<- 1’은 화살표가 가리키는 채널 c로 데이터를 보내겠다는 뜻이다.
<리스트 8> 이항연산자로 사용된 <- (send)
c := make(chan int)
c <- 1 //1을 채널 c로 보낸다.

goroutine간에 통신은 아래와 같은 동기화 특성을 가진다.
1) 보내는 동작 : 받는 쪽이 동일 채널에 대해서 유효할 때까지 블록된다.
2) 받는 동작 : 전달 쪽이 동일 채널에 대해서 유효할 때까지 블록된다.
따라서 goroutine간 통신은 동기화의 형태를 가지며 채널로 연결된 두 개의 goroutine은 통신 시점에서 동기화가 이뤄진다.

메모리 모델
메모리 모델이란? 스레드와 메모리간에 어떻게 상호작용이 이뤄지는지를 설명하거나 컴파일러가 어떤식으로 메모리 관리와 관련된 코드를 만드는지를 설명하는 것이다. Go언어의 메모리 모델은 두 개의 goroutine이 동일한 메모리에 접근할 때의 상호작용을 명시하고 있다.

왜 메모리 모델이 필요한 것일까? 대부분의 컴파일러는 컴파일시 성능 최적화를 위해 처리 순서를 조정하며 순서의 재조정은 논리적으로 전혀 다른 수행을 의미하진 않는다. 단일 스레드 수행 관점에서 동일하게 동작하는 수준의 재조정이 이뤄진다.

그러나 동시성 안에선 여러 스레드들이 동일한 메모리를 I/O할 경우 컴파일러의 순서 재종이 개발자가 의도한 동작을 반드시 보장하진 않는다. 스레드는 디버깅이 어려워 단순히 코드를 읽는 것만으로 실제 동작을 예측하기 쉽지 않고 문제가 된 상황을 재현하기도 만만치 않다. 메모리 모델에 대한 이해는 동시성을 갖는 프로그래밍에서 필수적임을 알아야 한다. 지금부터 Go언어가 제공하는 메모리 모델에 대해서 하나씩 살펴보기로 하자.
초기화 시점

기본적인 초기화 원칙은 단일 goroutine에서 초기화가 이루워는 동안 다른 새로운 goroutine이 생성돼도 기존 goroutine이 초기화를 마칠 때까지 새로운 goroutine은 실행되지 않다는 것이다. 몇 가지 경우에 대해서 구체적으로 알아보자.
· 패키지 p가 패키지 q를 import하면 p패키지 내의 동작은 q패키지의 init함수가 종료된 이후에 실행된다.
· 함수 main.main의 시작은 모든 init함수들이 끝나고 나서 일어난다.
· init함수 실행 동안 생성된 goroutine은 init함수가 완료된 후에 실행이 이루어진다.
goroutine의 생성
<리스트 9> 단항연산자로 사용된 <- (receive)
v = <-c // 채널 c에서 값을 받아서 v에 대입한다
<-c // 채널 c에서 값을 받아 사용하지 않고 버림.
i := <-c // 채널 c에서 값을 받아서 i를 초기화 한다.
<리스트 10>에서 go를 사용한 구문의 실행은 실제로 goroutine으로 실행되기 전 시점에서 실행이 이루어진다. 그러므로 “hello, world”가 출력되는 것을 볼 수 있다. 타이밍으로 예상해 보면 출력되는 시점은 아마 hello() 함수를 빠져나간 이후일 것이다.
goroutine의 소멸
goroutine의 종료 시점은 특정 이벤트 이전에 종료됨을 보장받지 못한다. <리스트 11>에서 예를 들어 a에 ‘hello’를 할당한 이후 다른 goroutine과 동기화 이벤트가 없으므로 print(a)로 ‘hello’가 출력됨을 확신할 수 없다. 의도한 동작을 보장 받으려면 a에 ‘hello’를 할당하거나 해당 goroutine이 종료된 시점에서 출력하는 goroutine에게 갱신된 a를 출력할 수 있다고 알려야 한다. 이는 다음에 다룰 채널을 이용한 동기화 이벤트로 할 수 있다.
<리스트 10> goroutine 실행 시점
var a string
func f() {
     print(a)
}
func hello() {
     a = "hello, world"
     go f()
}
<리스트 11> goroutine 종료 시점
var a string
func hello() {
     go func() { a = "hello" }()
     print(a)
}
채널 통신

goroutine간 동기화할 수 있는 주요 방법은 채널 통신이다. 채널을 생성하면 그 채널을 통해 goroutine간에 I/O를 할 수 있다. 
<리스트12>는 “hello, world”가 출력됨을 보장한다. a에 ”hello, world”를 할당한 것은 채널 c로 보내기 전에 실행된다. 또한 main에서 데이터를 받는 동작도 이미 Go언어 메모리 모델에서 살펴봤듯 a에 “hello, world”를 할당한 이후에 일어난다.
<리스트 12> 채널을 이용한 통신 예제
var c = make(chan int, 10)
var a string
func f() {
      a = "hello, world"
     c <- 0
}
func main() {
     go f()
     <-c
     print(a)
}
Lock
Go언어가 제공하는 sync 패키지에는 두 가지 lock 관련 데이터 타입이 있다. 바로 sync.Mutex와 sync.RWMutex이다.
<리스트 13>은 main함수에서 2번째 l.Lock() 이전에 f함수에서 l.Unlock()이 호출되는 구조로 모두 print(a)가 호출되는 시점 이전이므로 “hello, world”가 출력됨을 보장한다.
<리스트 13> Lock/Unlock을 이용한 동기화
var l sync.Mutex
var a string
func f() {
     a = "hello, world"
     l.Unlock()
}
func main() {
     l.Lock()
     go f()
     l.Lock()
     print(a)
}
Once

다양한 goroutine으로 복잡도가 높을 경우 sync.Once를 이용하면 안전하게 초기화할 수 있다.
<리스트 14>에서 goroutine은 두 번 doprint을 호출하지만 결과적으로 setup 함수는 한 번만 실행되며 setup이 반환될 때까지 대기한다. a는 “hello, world”로 정상적으로 초기화되며 예제는 두 번 출력된다.
<리스트 14> Once 이용하기
var a string
var once sync.Once
func setup() {
     a = "hello, world"
}
func doprint() {
     once.Do(setup)
     print(a)
}
func twoprint() {
     go doprint()
     go doprint()
}
채널을 이용한 소수 구하기

소수의 정의는 1과 수 자신으로만 나누어지는 자연수이다. <그림 9>는 소수를 구하는 방법 중 ‘아리스토텔레스의 체체’를 이용한 방법을 그림으로 표현한 것이다. 
<그림 9> 소수를 구하기 위한 체(sieve)

<리스트 15>는 Go언어 웹사이트에 공개된 채널을 이용한 동기화 방법을 설명하는 예제로 5개의 소수를 출력한다. 그럼 지금까지 배운 Go언어의 동시성에 대한 지식으로 코드가 어떻게 동작하는지 생각해보자.
<리스트 15> 소수 구하기

package main
import "fmt"
func generate(ch chan int){
     for i:=2; ; i++{
         ch <- i
     }
}
func filter(in, out chan int, prime int){
     for{
          i := <-in
          if i % prime != 0{
               out <- i
          }
     }
}
func main(){
     ch := make(chan int)
     go generate(ch)

     for i:=0; i<5; i++{
          prime := <- ch
          fmt.Println("prime : ", prime)
          ch1 := make(chan int)
         go filter(ch, ch1, prime)
         ch = ch1
     }
}

채널이 어떻게 동작하여 소수가 출력되는지 쉽게 이해가 되는가? 이해가 어렵다면 <리스트 16>처럼 출력문을 곳곳에 넣어보기 바란다. 
<리스트 16> 출력문으로 소수 구하기 예제 이해하기
package main
import "fmt"
func generate(ch chan int){
     for i:=2; ; i++{
          fmt.Println("generate : ", i)
          ch <- i
     }
}
func filter(in, out chan int, prime int){
     fmt.Println("filter create : ", prime)
     for{
          i := <-in
          fmt.Println("filter:", prime, "in : ", i)
          if i % prime != 0{
               out <- i
               fmt.Println("filter:", prime, " out : ", i)
          }
     }
}
func main(){
     ch := make(chan int)
     go generate(ch)

     for i:=0; i<5; i++{
          prime := <- ch
          fmt.Println("prime : ", prime)
          ch1 := make(chan int)
          fmt.Println("filter create before: ", prime)
          go filter(ch, ch1, prime)
          ch = ch1
     }
}

<리스트 16>은 필터(filter)가 생성되는 시점, 필터 내부로 값이 들어오는 시점과 나가는 시점을 화면에 출력하도록 출력문을 추가했다. 이제는 어떤 순서로 동작하는지 이해하기가 더 편해졌을 것이다. 더 명확한 이해를 위해 단계 단계마다 그림을 그리는 것도 좋은 방법이다. <리스트 17>의 1, 2에 대한 진행 상황을 그린 것이 <그림 10>과 <그림 11>이다. 채널을 이용한 경우 goroutine간에 동기화가 이뤄지므로 실행할 때마다 동작 순서에 큰 차이가 있진 않다. 
<리스트 17> 소수 구하기 실행 결과
generate : 2
generate : 3
prime : 2
filter create before: 2
filter create : 2
filter: 2 in : 3
generate : 4
filter: 2 out : 3
prime : 3
filter: 2 in : 4 --- 1
filter create before: 3
generate : 5
generate : 6
filter create : 3
filter: 2 in : 5
filter: 2 out : 5
filter: 3 in : 5
filter: 2 in : 6
filter: 3 out : 5
generate : 7
prime : 5 --- 2
...

<그림 10> 소수를 구하끼 위한 체(sieve) 생성과정-1
<그림 11> 소수를 구하끼 위한 체(sieve) 생성과정-2
채널을 처음 접하는 개발자라면 완벽한 이해를 위해 출력문을 살펴보거나 그림을 그려 이해하는 것도 한 방법이다.
이번 글에서 Go언어의 가장 큰 무기인 동시성을 살펴봤다. Go언어는 개발단계부터 동시성의 지원을 고려해 Go언어 내부적으로 동시성을 지원한다. 지난 2회에서 설명했듯 C언어 계열이기에 문법을 익히는데 큰 어려움은 없지만 채널이라는 개념은 많은 노력이 필요하다. 이해가 쉽지 않더라도 여유를 갖고 Go언어의 동시성을 하나씩 알아가면 동시성의 완벽히 이해할 수 있을 것이다.

Go언어를 보다 명확하고 쉽게 정리하고자 했지만 주변 일들로 실천하기가 쉽지 않았다. 하지만 월간 마이크로소프트웨어에 연재하며 막연히 공부했던 것들을 정리할 수 있는 기회가 됐고 이 글을 읽는 독자뿐만 아니라 필자에게도 의미 있는 시간이었다. 독자들도 Go언어에 대한 지식을 넓힐 수 있는 기회가 됐기를 바란다.
서두에 언급했듯 Go언어는 내년 초에는 더 안정적인 버전으로 우리에게 찾아온다. 실무에 바로 적용할 수 있을 만큼 충분한 완성도를 가질 것이라 확신한다. 여러분도 고퍼(gopher)가 되어 보는 것은 어떤가?

가장 많이 본 글