Software Workshop

시작하기 전에

image
(출처: https://parang0309.tistory.com/30)

7 Concurrency Models (in 7 Weeks)

이 중에서 서비스 개발 현업에서 사용되고 있는 개념이나 기술에는 Thread & Lock, Functional, Actor(개념만), Data Parallelism, Channel 등이 있음

Lambda Architecture는 big data 분야에서 널리 쓰이지만 프로그래밍 레벨로 들어온 것이 아니라서(아키텍쳐 레벨임) 여기서 다루지 않음

Concurrency vs Parallelism

Concurrency Parallelism
한글 용어 동시성 병렬성
범위 가상적/개념적 물리적
관점 설계 구현/하드웨어
의미 겹치는 시간 동안에 여러 태스크가 동시에 실행되는 것 여러 태스크가 다중 컴퓨팅 자원을 가지고 같은 시간에 실행되는 것
image
(출처: https://freecontent.manning.com/concurrency-vs-parallelism/)
image
(출처: https://freecontent.manning.com/concurrency-vs-parallelism/)
구어체로 표현하자면 한 번에 많은 일을 많은 일을 동시에
단일 코어에서 동시성을 가질 수 있음 병렬성을 가질 수 없음

Thread & Locks

Java의 Thread 예제

public class SimpleThreadExample {
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> System.out.println("Hello from Thread 1"));
        Thread thread2 = new Thread(() -> System.out.println("Hello from Thread 2"));
        thread1.start();
        thread2.start();
        
        try {
            thread1.join();  // 첫 번째 쓰레드가 완료될 때까지 대기
            thread2.join();  // 두 번째 쓰레드가 완료될 때까지 대기
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Hello from Thread 1
Hello from Thread 2
(순서 뒤바뀔 수 있음)

C & POSIX Thread 예제

#include <stdio.h>
#include <pthread.h>

void* printHello(void* id) {
    printf("Hello from Thread %ld\n", (long)id);
    return NULL;
}

int main() {
    pthread_t threads[2];

    for (long i = 0; i < 2; i++) {
        pthread_create(&threads[i], NULL, printHello, (void*)i);
    }

    for (int i = 0; i < 2; i++) {
        pthread_join(threads[i], NULL);
    }

    return 0;
}

Hello from Thread 0
Hello from Thread 1
(순서 뒤바뀔 수 있음)

C++11의 Thread 예제

#include <iostream>
#include <thread>

void printHello(int id) {
    std::cout << "Hello from Thread " << id << std::endl;
}

int main() {
    std::thread t1(printHello, 1);
    std::thread t2(printHello, 2);

    t1.join();
    t2.join();

    return 0;
}

Hello from Thread 2
Hello from Thread 1
(순서 뒤바뀔 수 있음)

Thread의 공통적인 특징

Thread의 장단점

Race Condition

image
(출처: https://comic.browserling.com/53)

Thread와 관련하여 알아두면 좋은 상식: detach

Thread와 관련하여 알아두면 좋은 상식: Thread Local Data

동기화 기본연산(Synchronization Primitives)

기본연산 \ 언어 Java Javascript C++ Python
Mutex(lock) O
(ReentrantLock)
?
(Atomics)
O
(C++20)
O
Semaphore O X O
(C++20)
O
Critical Section O
(synchronized)
X X X
Conditional Variable O X O O
Read-Write Lock O X O X
(Cyclic) Barrier O X O
(C++20)
O
Countdown Latch O X O
(C++20)
X
Phaser O X X X
Exchanger O X X X

기본연산의 주요 개념

의문점

동시성 기법의 과거와 미래

과거: Thread의 종류와 역사

미래

Virtual Thread

특징

예제

public class VirtualThreadExample {
    public static void main(String[] args) {
        for (int i = 0; i < 1_000_000; i++) {
            Thread.startVirtualThread(() -> {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }
}

Coroutine

Coroutine에 앞서 Promise & Future

Javascript의 Promise & Future 예제

let myPromise = new Promise(function(resolve, reject) {
    try {
        ...
        resolve("완료");
    } catch (error) {
        reject(new Error("에러 발생!"));
    }
});
myPromise.then((result) => {
    console.log(result);
}).catch((error) => {
    console.error(error);
});

C++의 Promise & Future 예제

std::promise<int> p;
std::future<int> f = p.get_future();
std::thread( [&p]{ p.set_value_at_thread_exit(9); }).detach();

this_thread::sleep_for(chrono::seconds(1));
try {
    f.get();
} catch (const std:exception& e) {
    ...
}

Java의 Promise & Future 예제

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "future example");
future.get();

Coroutine에 앞서 Generator

Python의 Generator 예제

def test_generator():
    yield 1
    yield 2
    yield 3

gen = test_generator()
next(gen)
next(gen)
next(gen)
next(gen) # exception

Python의 초기 Coroutine 예제

def simple_coroutine():
    print('Coroutine started')
    x = yield
    print('Coroutine received:', x)

coro = simple_coroutine()
next(coro)
coro.send(42)

Coroutine이란

image

Coroutine의 내부 구현 (저수준) - resume 기능 관점에서

#include <stdio.h>
#include <setjmp.h>

jmp_buf main_routine_buf, coroutine_buf;

void coroutine() {
    printf("Coroutine start\n");
    if (setjmp(coroutine_buf) == 0) {
        longjmp(main_routine_buf, 1);
    }
    printf("Coroutine end\n");
}

int main() {
    printf("Main start\n");
    if (setjmp(main_routine_buf) == 0) {
        coroutine();
    } else {
        printf("Back in main\n");
        longjmp(coroutine_buf, 1);
    }
    printf("Main end\n");
    return 0;
}

Main start
Coroutine start
Back in main
Coroutine end
Main end

현대적인 async/await 스타일의 Coroutine

image
(출처: https://www.reddit.com/r/programminghumor/comments/j6cvwj/async_await/)

Javascript의 Coroutine 예제

async function fetchData(url) {
    let response = await fetch(url); 
    let data = await response.json(); 
    console.log(data);
}

fetchData("https://api.example.com/data");

Python의 Coroutine 예제

import asyncio

async def say_hello(delay):
    await asyncio.sleep(delay)
    print(f"Hello after {delay} seconds!")

async def main():
    task1 = asyncio.create_task(say_hello(1))
    task2 = asyncio.create_task(say_hello(2))
    
    await task1
    await task2

asyncio.run(main())
import asyncio
import time

def blocking_io():
    time.sleep(1)
    return "I/O operation completed."

async def main():
    loop = asyncio.get_running_loop()
    result = await loop.run_in_executor(None, blocking_io)
    print(result)

asyncio.run(main())

Python의 Coroutine Task

import asyncio

async def worker(name, seconds):
    print(f"Worker {name} started, will sleep for {seconds} seconds")
    await asyncio.sleep(seconds)
    print(f"Worker {name} finished after {seconds} seconds")

async def main():
    tasks = [
        worker("A", 5),
        worker("B", 3),
        worker("C", 2),
        worker("D", 4)
    ]
    await asyncio.gather(*tasks)

asyncio.run(main())

C++20의 Coroutine 예제 (기본)

#include <iostream>
#include <coroutine>

using namespace std;

struct task {
    struct promise_type {
        task get_return_object() { return {}; }
        suspend_never initial_suspend() { return {}; }
        suspend_never final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() {}
    };
};

task simple_coroutine() {
    cout << "inside coroutine!" << endl;
    co_return;
    cout << "end of coroutine" << endl;
}

int main() {
    cout << "main start" << endl;
    simple_coroutine();
    cout << "main end" << endl;
    return 0;
}

main start
inside coroutine!
main end

task 구조체와 promise_type 구조체는 사실 상 추상 클래스에 가까워서 필요한 기능을 추가 구현해야 할 수도 있음

C++20의 Coroutine 예제 (resume)

#include <iostream>
#include <coroutine>

using namespace	std;

struct task {
    struct promise_type {
        task get_return_object() { return task(handle_type::from_promise(*this)); }
        suspend_always initial_suspend() { return {}; }
        suspend_never final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() {}
    };
    using handle_type = coroutine_handle<promise_type>;
    handle_type coro;

    task(handle_type h) : coro(h) {}
    ~task() { if (coro) coro.destroy(); }
    void resume() { if (!coro.done()) coro.resume(); }
};

task simple_coroutine() {
    cout << "Before suspension" << endl;
    co_await suspend_always{};
    cout << "After suspension" << endl;
    co_return;
}

int main() {
    auto t = simple_coroutine();
    cout << "Coroutine created but not yet resumed." << endl;
    t.resume();
    cout << "Coroutine resumed and completed." << endl;

    return 0;
}

Coroutine created but not yet resumed.
Before suspension
Coroutine resumed and completed.

image
(출처: https://www.reddit.com/r/ProgrammerHumor/comments/10s12uf/c_meetup/)

C++의 현실적인 대안 예제

#include <iostream>
#include <future>

int main() {
    auto future1 = std::async(std::launch::async, []() {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        return 42;
    });

    auto future2 = std::async(std::launch::async, []() {
        std::this_thread::sleep_for(std::chrono::seconds(2));
        return 99;
    });

    int result1 = future1.get();
    int result2 = future2.get();

    std::cout << "Result from task1: " << result1 << std::endl;
    std::cout << "Result from task2: " << result2 << std::endl;

    return 0;
}

Result from task1: 42
Result from task2: 99

Coroutine의 통신 방법

Channel

Go lang의 Channel 예제

package main

import (
	"fmt"
	"time"
)

func sender(ch chan int) {
	for i := 0; i < 5; i++ {
		fmt.Println("Sending:", i)
		ch <- i  // 값을 채널에 송신
		time.Sleep(time.Second)
	}
	close(ch)
}

func receiver(ch chan int) {
	for {
		value, ok := <-ch  // 채널로부터 값 수신
		if !ok {
			fmt.Println("Channel closed!")
			break
		}
		fmt.Println("Received:", value)
	}
}

func main() {
	ch := make(chan int)
	go sender(ch) 
	go receiver(ch) 
	time.Sleep(6 * time.Second)
}

Sending: 0
Received: 0
Sending: 1
Received: 1
Sending: 2
Received: 2
Sending: 3
Received: 3
Sending: 4
Received: 4
Channel closed!

Wrap-up

추상화 단계

요즘 기술 트렌드

흑묘백묘론

image
(출처: https://namu.wiki/w/암컷이든 수컷이든 맛있으면 그만 아닐까%3F)

자신이 사용하는 언어에서 제공하는 적당한 수준으로 추상화된 기술을 적절히 구사하면 됨