Skip to content

Instantly share code, notes, and snippets.

@cr0sh
Last active March 6, 2016 12:46
Show Gist options
  • Save cr0sh/2c8fbbd5e5b63e2d0519 to your computer and use it in GitHub Desktop.
Save cr0sh/2c8fbbd5e5b63e2d0519 to your computer and use it in GitHub Desktop.
Synopsis Language draft #2(Korean)

Synopsis language

목적: 메모리 오류를 방지하는 동시성 프로그래밍

사전 고찰

동시성 프로그래밍에서의 위험 요소: Race conditionDeadlock

Rust 언어의 해결 방안: move-by-default, ownership, lifetime

  • = 연산자는 변수의 ownership(소유권)을 이동시킴
  • 소유권이 없는 변수는 사용 불가
  • 변수가 정의된 scope를 벗어나면 자동 해제(lifetime 소멸)
  • 역시 scope 밖에서 사용 불가
  • 모든 것이 이동이면 불편하므로 ownership borrowing(소유권 대여) 가능
  • &(Read-only)를 여러 개 빌리거나 &mut(Read-Write)를 한 개 빌릴 수 있음
  • 대여된 ownership 반환 전까지는 원본 변수 사용 불가
  • 목적: Race condition 방지
  • 응용: 반복자 무효화(iterator invalidation?) 방지, 뮤텍스 유출 방지 등
  • 이러한 작업들이 모두 컴파일 타임에 이루어지므로(zero-cost abstraction 등 포함) 성능 손해가 없음

Rust의 장점:

  • C++동등/이상 수준의 zero-cost 추상화
  • GC 없음(모든 메모리 관리는 Compile-time에 처리)
  • Mozilla, Samsung의 지원
  • Race condition, Dangling pointer 등의 메모리 오류를 컴파일 상황에서 방지

Rust의 단점:

  • 모든 메모리 오류를 예방할 수는 없음(특히 Deadlock)
  • 높은 난이도(C++보다는 나은 것으로 알려짐)
  • 아름답지 않은(Not pythonic) 코드(기호로 점철됨)

Go의 동시성 지원: Goroutine이 핵심

  • Erlang에서 영향을 받은 모델
  • Go runtime에서 자체 관리하는 경량 마이크로스레드
  • 6KB 정도의 Stack 메모리만 할당(수천 개를 생성해도 부하 없음)

Go의 특징

  • C-family 문법(struct, 함수 선언 등) + Pythonic한 문법(세미콜론 불필요, 한 번에 여러 변수 할당 등)
  • 덕 타이핑 기반의 OOP(struct에 함수를 붙여서 사용, 상속 X, 인터페이스 기반)
  • Goroutine
  • Channel
  • Unbuffered: 동기, Buffered: 버퍼가 찰 때까지 비동기
  • 자료형에 send 전용/receive 전용 명시 가능
  • First-In-First-Out
  • 명시적 동기화에 이용(chan struct{} 등으로 signal 전달 등)
  • C와의 접착성이 좋음(cgo)
  • 매우 방대한 툴(GoFmt, pprof, heapdump, race detector)
  • 다양한 디버깅 툴(pprof, heapdump, tracer, race detector)이 기본 제공되나 디버깅이 어렵다는 평가가 있음(글쎄?)
  • GC 기반(Go 1.5부터 성능 개선, 최악 지연 시간이 <10ms)
  • 매우 빠른 컴파일 속도

Go의 동시성 모델

  • 고전적 방법(Mutex): sync.Mutex, sync.RWMutex 사용
  • 장점: 확실한 상호 배제로 Race condition 방지
  • 단점: Deadlock의 위험(두 번 Lock하기, 서로 맞물리는 Lock 의존성) (Dining philosophers 문제)
  • Gophers' method(Go Concurrency Patterns, Advanced Go Concurrency Patterns, http://blog.golang.org/context, http://blog.golang.org/pipelines): Channel을 이용한 명시적 동기화
  • 모든 Goroutine은 자기에게 할당된 변수만 접근, 상대 간섭은 Channel을 이용
  • 다른 Goroutine에 접근하려면 클로저(func() {})를 Channel로 전달
  • sync.WaitGroup 또는 chan struct{}를 활용해 다른 Goroutine들과 동기화
  • 장점: Mutex를 사용하지 않으므로 Mutex에 의한 Deadlock 위험이 없다. 제어 흐름을 간단히 표현 가능하다
  • 단점: 채널 남발 시 비동기 전송으로 인해 역시 Deadlock 가능성이 있음

Synopsis 언어의 목표:

  • Rust의 난해함을 해결(더 쉬운 언어)
  • Rust보다 간결한 문법(Go-Style?)
  • Go/Rust에서 해결하지 못한 Deadlock 이슈 해결
  • Rust의 block is an expression 차용
  • 속도/안정성 보다는 Deadlock 해결을 위한 Concept 제시가 주된 목적

Synopsis Concept

  • No Mutex(컴파일타임 추론)
  • No Class(Go와 같은 구조체에 대한 함수 정리만을 지원)
  • Buzzer(Go의 channel broadcasting과 유사)
  • RunForm
  • 변수 Import/Export
  • Block 스코프
  • 정적 타입

RunForm on Synopsis

runform R1 { // async runform 선언도 가능
    int A = 9 // 기본값
    int B = 7
    int NeedDef // 선언 필요
    // RunForm Field의 lifetime은 RunForm 내에서 계속 유지
} Form1 (arg1, arg2) { // arg1, arg2의 lifetime은 Form1 내부에서만 유지
    // Do something
    arg2 // arg2의 lifetime을 다음 Form으로 연장
} Form2 (arg1 = 3) { // arg1 기본값 설정
    // Do something
} { // Anonymous Form(Form2 후 바로 실행, 연장하지 않은 Form2의 모든 lifetime 소멸)
    // Do something
} Form3 { // 인자 없을 시 () 생략가능
    // Do something
    go Form1(1, 2)// 즉시 sleep 후 Form1로 점프
    // 따로 인자가 필요 없는 Form2는 go Form2와 같이 () 생략 가능
}

R1{NeedDef:3}(2,3)(3)() // R1 생성 및 R3까지 바로 실행
var a R1
a = R1{NeedDef: 3}(2, 3) // R1 생성 후 Form1까지 실행
if stat a == R1.Form1 { // Form1 구동이 끝났으므로 Form1 상태. 조건문은 참
    // 이 코드는 실행됩니다
}
// Async RunForm의 경우 wait문을 이용해 Form의 구동이 끝날 때까지 대기 가능
// 만약 Sleep된 Form 이외의 모든 Form이 인자 명시가 필요 없는 상황이라면, ~ 으로 모두 실행 가능(form batching)
// ex) R1{NeedDef: 3}(2,3)~

Variable export/import, mutex 관리, drop statement

Synopsis에서는 지역 변수를 외부에서 참조할 수 있다.

async runform R2 {
    int A = 3 // 다른 Thread에서 A에 접근 시 Mutex를 잠근다(Lock).
} Form1 : (aa) {// aa는 Form1 Sleep 또는 drop까지 Lock 상태이다.
    aa = 3
} { // 이 시점에서 잠시 Form1은 Sleep 상태가 되며 A, aa의 Lock이 해제된다. aa의 lifetime은 소멸된다.
    // Do something
}

var a R2
a = R2{}

// 주의: Async RunForm이 아닌 경우에는 같은 Thread에서 import가 불가능하다.
// aa가 아직 export되지 않았으면 export될 때까지 대기한다
(a.aa) { // 여러 개인 경우 콤마로 구분
    // aa는 이제 Lock되었다.
    // Do something with aa
} // aa의 Lock은 다시 해제된다
// Block은 Expression으로 작용하므로 Rust와 같이 값을 돌려줄 수도 있다.
runform R3 {
} Form1 (arg1 = 3) : (a.aa, b.bb) {
    // Do something with a.aa, b.bb
    drop a.aa // Unlock/drop a.aa
    // Still b.bb is available/locked
}

Form에서 import하려면 : 문자를 이용한다.

Grouped RunForm

grouped runform R3 {
    // ...
} F1 {
    // ...
}
R3{}(2)~  // 기존 RunForm 실행과 동일

Group 속성을 가진 RunForm은 하나의 Thread를 할당받고, 이 Thread 내에서 Async RunForm들이 구동된다. Form Sleep 상태에서는 다른 RunForm으로 실행 상태가 yield될 수 있다.


RunForm의 목적

  • 프로그래머가 .Lock(), .Unlock()을 사용하지 않고도 Multi-Thread 상황에서 변수 접근을 처리하는 방법을 제공
  • Form Sleep 상태에서 자동적으로 Unlock되고, Form 내부에서는 drop하지 않는 이상 계속 Lock을 가지고 있음
  • 변수의 Lock을 release하기 위해서 짧은 Anonymous Form들을 이어붙여 자주 Sleep시키는 것을 장려
  • function, class, struct의 융합
  • function은 Field가 없는 RunForm, struct는 Form이 없는 RunForm이 됨(주: RunForm에서는 명시적 return이 되지 않으므로 return value가 있다면 이를 저장하는 Field는 필요하다.)

Grouped RunForm의 목적

  • 프로그래머가 비슷한/동일한 작업을 묶어서 비동기로 처리해야 하는 경우(세션 처리, 여러 DB로 쿼리 등)를 만났을 때, Grouped RunForm 사용을 장려
  • 각 RunForm Group은 하나씩 Thread를 받고, 각 Form은 Sleep 전까지는 yield되지 않으므로 여러 RunForm 간의 흐름을 보장 가능

Advanced RunForm Tricks/Usages

Anonymous Form의 특성

runform R1 {
} {
    // Do something
} {
    print("Hello!")
}

runform R2 {
} F1 {
    // Do something
} {
    print("Hello!")
}

var a R1
a = R1{} // prints "Hello!"

var b R2
b = R2{} // does nothing
b() // prints "Hello!"

첫 Form이 Anonymous Form인 경우, runform은 정의된 직후에 바로 첫 Anonymous Form을 실행한다. Named Form의 경우에는 수동으로 ()를 이용해 invoke해줘야 한다.

wait문

runform R1 {
    int A = 3
} F1 {
    // Do something
} {
    print("Hello!")
}

var a R1
a = R1{A: 2}
wait a() { // R1이 요쳥된 모든 Form 작업을 처리할 때까지 대기
    // 이 시점에서 a의 변수들은 모두 Lock되어 있다.
}

Async RunForm과 조합하면 pthreads의 join 함수와 유사하게 사용할 수도 있다. 각 Anonymous Form간의 sleep 구간은 wait문으로 block되지 않음을 유의하라.


Synopsis rules

  • 모든 지역 변수들은 Lifetime을 가지며, 연장을 명시하지 않는 한 {} 블럭 내부에서만 유효하다.
  • Race 가능성이 있는 것으로 판단되는 변수(여러 Thread에서 접근)는 모두 변수 각각에 Mutex를 부여한다.
  • RunForm 밖에서 import하는 경우 해당 import scope 내부에서 Lock을 가진다.
  • RunForm Field 변수는 Form scope 내부에서 Lock을 가지고, scope간 sleep 상태에서는 일시적으로 Unlock된다.
  • RunForm에서 export된 지역 변수는 export된 block 내부에서는 Lock을 가지고, 해당 block이 끝나면 Unlock된다.
  • RunForm 내부에서 import하는 변수는 import된 block 내부에서만 Lock을 가진다.
  • 모든 RunForm의 상태(stat RunForm)는 init으로 시작해 run과 sleep을 반복한다. 마지막 Form의 실행이 끝났다면 done 상태가 된다.
  • 상태 비교는 if stat r == R1.Form1.running {} 과 같이 한다. init와 done의 앞, Anonymous Form의 run/sleep에는 Form 이름을 붙이지 않는다.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment