블럭,논블럭,동기,비동기 이야기  

 

블록,논블럭,동기,비동기를 구분하는 것에 대한 글들이 많이 있는데, 어렵게 풀어내는 거 같아서 나름 간단하고 분명하게 구분해 보는 글을 작성 해 본다. 근데 함정이 있는데 분명하게 정답을 말해 준다는게  아니다. 분명하게 정답이 없으며, 불분명하다고 말해주려는 것이다. ㅎㅎ  면접시나 시험지에 적을 정확한 정답을 몰라서 혹시 불안해 하시는 분이 있다면 이 글을 읽고 안심하셔도 될 것이다.(뭐 시험관이 잘못알고 있는것 까지 책임지진 못하겠다. 과감히 논쟁하시라~~ㅎ) 구체적으로 블럭/논블럭에 대한 구분은 비교적 명확하다. 동기/비동기로 넘어가면 말하는 상황에 따라서 조금 달라지기 시작한다. 이제 조합하기 시작하면 문제가 발생하기 시작한다. 이 글에서는 이것의 구분에 대한 설명을 해드릴 것이지만, 구분을 굳이 왜 해야하는가? 라는 의문이 생길 수도 있을 것이다. 그렇다면 내 글의 의도가 먹힌 것이다. 사실 이것은 구분을 명확히 해야하는 문제라기 보단, 이런식으로 시스템이 작동 하기도 하는 구나 라는 "감" 을 잡으면 되는 문제이다. 

여기서 내가 내리는 정의 또한 "정답" 이 아니다. 애초에 정답이 없는 문제라고 생각하고 유연하게 바라보자. 즉 1+1=2 같은 종류의 문제라거나, 자바에서 class 의 정의는? 같은 문제와는 다르다. 디자인패턴 같은 느낌이다. "의도"는 있지만 "구현"은 제각각인.. 

처음에는 각각의 정의를 내려보고, 그 담엔 우체국을 예로 들어서 이야기 식으로 구분을 해 보며, 마지막으로는 코드를 통한 예를 통해 그 "감"을 잡아내는 수확을 얻도록 하자.

 

@ 다 읽기 귀찮고 감 만잡으려면 3번 우체국이야기는 꼭 읽자

@ 애초에는 I/O 과 연관되어서 정의내리는 것이었는데, I/O 상관없는 비동기가 자주 사용되면서 더 희미해졌다.

 

 

1. 개별 정의


블럭/논블럭 

- 블럭/논블럭는 함수호출에서의 이야기이다.(기술적으로 명확히 구분된다.) 
- A 라는 함수를 호출했을때, A라는 함수를 호출 했을 때 기대하는 행위를 모두 끝마칠때까지 기다렸다가 리턴되면, 이것은 블로킹 되었다고 한다.
- A 라는 함수를 호출 했는데, A라는 함수를 호출 했을 때 기대하는 어떤 행위를  요청 하고 "바로" 리턴되면 이것은 논블럭킹 되었다고 한다.

동기/비동기

- 동기/비동기는 행위에 대한 이야기이다.
- 여기서 "행위"는 단순히 서로 다른 쓰레드 or 프로세스 or  서버에서 일어나는 일련의 동작들 이라고 치환해서
  생각하면 이해하기는 쉽다. 
- 동시성(concurrent) 의 문제이지 병행성(parallel ) 과는 무관하다. (병행성에 대한 의식은 잠시 잊어버리자) 

- A 라는 행위와 B 라는 별개의 행위가 있다고 하자. A 라는 행위와 B 라는 행위가 동시(or 순차적이지 않다면)에 실행되고 있으면 비동기라고 한다. 여기서 제약이 하나 있는데 A,B 행위 사이에는 인과관계가 있어야 한다. 즉 웹서버를 예로 들어서 멀티쓰레드로 각각 A와B가 다른 클라이언트와 작업 할 때 둘은 동시에 작업하고 있지만, 둘의 인과관계는 없지 않나? 이땐 비동기라고 볼 수 없다. A라는 행위의 결과를 B라는 행위에서 언젠간 이용하게 될 때 비동기라고 본다. 

- A라는 행위와 B라는 행위가 순차적으로 작동한다면 동기라고 한다. 
- 동기적 행동에는 하나가 더 있다. A라는 행위가 별개의 것이 아니라, B라는 행위를 관찰하는 행위라면 이것이 동시에 일어나더라도 동기이다. 기술적으로 말해서 A라는 쓰레드와 B라는 쓰레드가 따로 돌아 간다고 해도, 어떤 하나의 행위가 다른 행위에 밀착되어 있다면 두 행위가 다른 쓰레드에서 벌어지더라도 동기란 말이다. 관찰하는 행위라는 말자체가 정확한 기술적 구분이 되는게 아니기 때문에 추상적이라는 표현을 사용한 것이며, 이 글의 가장 불분명한 요소 중 하나이니 잘 기억해 두도록하자. 이해가 안가면 다음의 우체국 예제와 실제 코드예제를 통해 이해할 수 있을 것이다.

사실 여기까지만 읽으셔도 된다. "굳이" 조합까지는 생각 할 필요는 없다. 조합은 내 개인적 상상력의 산물이다. 

2.조합 정의  
 
블럭/논블럭과 동기/비동기를 조합해서 상상을 해보자. 


블럭/동기 

A가 실행되다가 B라는 일을 수행하는 함수를 호출해서 B를 시작한다. B라는 일이 끝나면 함수를 리턴한다. A와 B는 순차적으로 진행되기 때문에 동기이며,  B라는 일을 하는 함수를 호출하고 그 일이 끝나고 나서야 리턴되므로 블럭된 것이다. 따라서 블럭/동기 

 

블럭/비동기 

어떻게 블럭되었는데 A,B라는 일이 동시에 일어나는가? 설명을 들어보고 이런 경우를 말하는구나라는 "감"을 잡아보자.

일단 A는 B라는 일을 시킨다. 그리고 바로 리턴하고 (여기서는 논블럭)  B는 일을 시작하고, A도 자신의 일을 한다. A는 중간에 B라는 일이 하는 중간 결과를 보고 받아서 처리해야한다. A는 B에게 요청을 해서 중간결과를 기다린다(블록), 요청의 결과를 받고 나서 그 결과를 이용해서 A는 자신의 일을 처리한다. 동시에 B 는 또 자신의 일을 동시에 한다. (비동기) A는 다시 B에게 중간결과를 요청해서 기다린다 (블록) , 요청의 결과를 받고 A는 자신의 일을 , B는 자신의 일을 한다. 반복된다.

이 글을 읽고, 사실 갸우뚱 해야한다. 중간에 블록되는 동안에는 "동기" 라고 말 할 수 있기 때문이다. 즉 어느 한 순간에 대해 해석하자면 틀릴 수도 있는것이다. 즉 처음부터 말해왔듯이 "정답"이 존재하지 않는다. 다만 이런 패턴들이 분명히 사용되고 있구나라고 감을 잡는게 목적이다.

 

논블럭/동기 

이것이 예도 위의 블럭/비동기와 비슷한데 조금 다른 늬앙스에 대해서 "감"을 잡아보자.

이것도 역시 A는 B라는 일을 시킨다. 바로 리턴한다. (논블럭)  B는 일을 시작하는데, A는 자신의 일을 하지 않는다. A의 하는 일이란 그저 B가 하는일을 확인하는 것이다. B가 결과 보고(중간 보고가 아니다) 를 했는지를 확인하는 함수를 호출하고 ,바로 리턴한다 (논블럭) 즉 결과 보고를 받을 때 까지 기다리는게 아니라, 결과 보고가 나왔는지 확인하고 바로 리턴하는 것이다.  이 짓을 계속한다. 즉 함수를 계속 논블럭으로 호출되긴 하나, A는 그저 B를 염탐할 뿐이다. 이 상태를 말한다. 그냥 염탐하지 말고 B가 일을 모두 끝마치고 리턴되길 기다리지 ;;; (그냥 블럭/동기로 하는게 나은 상황이 연출된다) 
 
이후에 B가 결과보고를 하면,B는 자신의 일이 끝난 것이고 A는 이제서야 자신의 일을 처리하게 된다. 즉 순차적이라는 말이다. 따라서 동기~
 

논블럭/비동기 

간단하다. A는 B의 일을 시작시키고 바로 리턴한다 (논블럭) 그리고 A와B는 각자 자신의 일을 한다 (비동기) 

 

 

2. 실행활에서 일어나는 우체국 이야기로 풀어보자.

 

블럭/동기 

우체국에 배달 트럭들이 줄을 서 있다. 우체국에 들어오는 물품들을 싣기 위해서인데,
 
- 1번 트럭이 우체국에 내 것들을 가져와주세요 요청하고 기다린다. (블럭)

- 우체국은 1번 트럭에게 주기 위한 물건들을 찾아서 싣기 시작한다. 

- 2번트럭은 1번트럭에 물건이 다 싣기를 기다린다. (블럭)

- 3번 트럭도 기다린다. (블럭)

- 1번트럭이 물건을 싣고 떠나면, 우체국은 이제 2번 트럭의 물건을 찾아서 싣는다. (동기) 

모든 일들이 순차적으로 일어 난다 (동기) 

 

블럭/비동기 

우체국에 가서 내가 필요한 물품은 무엇이라고 접수원에게 말을 하고 집으로 돌아온다.

- 우체국은 물품을 준비하고, 나는 집에서 집안 청소를 한다. (비동기)

- 우체국에 전화 해서 접수원과 통화한다. 물품이 준비되었냐고 물어본다. 접수원은 준비될 때 까지 기다리라고 한다. 나는 하염없이 기다린다 (블럭)

- 접수원이 준비됬다고 말한다. 나는 트럭을 가지고 우체국으로 가서 물건을 싣고 온다.

- 우체국은 자신의 일을 하고, 나는 싣고 온 물건을 배달한다 (비동기) 

중간에 블럭되는 지점이 있지만, 그 이전과 이후에는 각자 자신의 일을 한다. 

 

논블럭/동기 

우체국에 가서 내가 필요한 물품은 무엇이라고 접수원에게 말을 하고 집으로 돌아온다.

- 우체국은 물품을 준비하고, 나는 전화기를 붙잡는다.

- 우체국에 전화 해서 접수원과 통화한다. 물품이 준비되었냐고 물어본다. 접수원은 안됬다고 말한다. 나는 전화를 바로 끊는다. (논블럭)  

- 전화를 끊고, 집안 청소를 하는게 아니라, 다시 우체국에 전화한다. 안됬다고 하면 바로 끊는다 (논블럭)

- 계속 반복적으로 전화한다 (논블럭이며, 나는 내 일을 하는게 아니라 우체국의 일에 매달리고 있으므로 동기) 

- 이번 전화에는 접수원이 준비됬다고 말한다. 나는 트럭을 가지고 우체국으로 가서 물건을 싣고 온다.

- 나는 싣고 온 물건을 배달한다.

중간 중간 논블럭으로 전화를 바로 끊지만, 끊고 나서 바로 또 전화를 하므로 동기

 

* 이 경우에 내가 배달하는 동안에는 현실과 좀 다르지만 우체국은 쉰다고 생각 해야한다.  (동기

 

논블럭/비동기 

우체국에 가서 내가 필요한 물품은 무엇이라고 접수원에게 말을 하고 트럭을 놓고 집에 온다. (논블럭)
트럭(버퍼) 크기가 크다면 우체국에서 많이 채워 줄 것이다. (하지만 좀 더 시간이 걸리겠지) 

- 우체국은 물품을 준비하고, 나는 집에 와서 내일 을 한다 (비동기)

- 전화 따위는 하지 않는다. 우체국에서 알아서 트럭에 짐을 채워서 나에게 트럭이 준비됬으면 연락 할 것이기 때문이다. 

- 트럭이 가득 찼다고 연락이 왔다. 나는 트럭을 가지고서 배달을 시작하고 우체국은 자신의 일을 한다.

이것이 논블럭/비동기이다. 완전 효율적이지 않는가? 

하지만 이것도 병목지점이 있다. 어디일까? 
그렇다. 이 배달기사는 트럭이 한대 뿐이다. 
트럭이 한대 뿐이기 때문에, 배달하는 동안에는 우체국에서 또 다른 짐을 싣지 못한다.
 
어떻게 해결 할까? 간단하다. 트럭을 2개 만드는 것이다 (기술적으로 버퍼를 2개)
그러면 한대는 배달하는 동안에 우체국에 다른 한대를 맡겨 놓는 것이다.
이렇게 되면 배달일 끝날 쯤에는 우체국에 가있는 트럭은 가득 차 있을 것이고, 나는 연속적으로 배달을 할 수 있어서 돈을 많이 벌 수 있을 것이다.
 
여기서 끝이 아니다 병목이 또 하나 있다. 이번엔 무엇인가?
그렇다 배달기사가 하나라는 것이다. 
 
우체국에서 또 다른 트럭이 벌써 가득 차 있다고 연락이 왔지만, 배달중이라 그것을 처리 할 수가 없다. 
이때 어떻게 해야하나? 
그렇다 배달알바를 구하면 된다. 한대의 트럭이 준비되면 그 트럭이 짐을 3등분해서 배달알바 3명에게 나눠준다. 또 다른 트럭이 준비되면 , 배달알바가 끝난 알바생에게 나눠주거나 또 다른 알바생에게 나눠주면 된다.
 
일의 크기에 따라서 알바생을 늘리면 되는 것이다. 이 알바생이 소프트웨어에서 무엇일까?
그렇다~~ 멀티쓰레드이다.
 
비동기 / 싱글쓰레드로 짧게 짧게 일하는 곳 (Node 비동기 서버에서 간단한 리턴만 서비스 하는 곳)에서는 멀티 쓰레드를 굳이 도입하지 않아도 효율적이지만, 백단에서 먼가 해야 할 것이 많다면 (CPU intensive) 이렇게 멀티쓰레드를 추가 해주면 성능이 대폭 올라 갈 것이다.
 

3. 실제 코드 예제로 풀어보자.

 

블럭되어 동기식으로 일처리 - javascript

const fs = require('fs'); 
const data = fs.readFileSync('/file.md');

파일 다 읽을 때 까지 함수가 멈춰져 있으며, (블럭) 다른일도 못한다 (동기)

논블럭되어서 비동기식으로 일처리 - javascript

const fs = require('fs'); 
fs.readFile('/file.md', (err, data) => {  
	// readFile 호출해 놓고 바로 리턴한다.    
	if (err) throw err;  // 하지만 이 일에 대한 인과관계 장치를 마련해 둔다.
 });  ... 다른일을 한다 ...
파일 읽으라고 명령해 두고 바로 리턴(논블럭) 바로 다른 일을 한다 (비동기) 


논블럭인데 비동기는 아니다  - golang
func start_server() {     
	 l, err := net.Listen(CONN_TYPE, CONN_HOST+":"+CONN_PORT)    
	 defer l.Close()     
	 for {        
		 conn, err := l.Accept()         
		if err != nil {            
			log.Print(err)             
			continue         
		}    
	 go handle_client(conn)    
 } 
}
go 루틴으로 (go handle_client) 개별 처리하게 만들고 바로 리턴해서 논블럭이라 할 수 있다. 하지만 go 루틴으로 분기된 것들은 멀티쓰레드로 동시에 일은 하지만 인과관계 부족으로 동기/비동기와 무관하다고 볼 수 있다.

논블럭이면서 비동기  - golang

아래의 예처럼 go 루틴으로 분기시킨 후에 go 채널로써 상호작용(인과관계)를 발생시키는 경우에는 비동기식이라 할 수 있을 것이다.
 sigs := make(chan os.Signal, 1)  
 signal.Notify(sigs)   
go func() {    
    s := <-sigs     
    log.Printf("RECEIVED SIGNAL: %s",s)    
    AppCleanup()    
    os.Exit(1)  
}()
 
 
논블럭/동기  - Scala 
 
val myFuture: Future[String] = Future {      
	val f = Source.fromFile("build.sbt")         
    try 
    	f.getLines.mkString("\n") 
    finally
    	f.close()     
}         
    if myFuture.isCompleted {              ....     }        
    Thread.sleep(100)      
    if myFuture.isCompleted {              ...     }      
    Thread.sleep(100)         
    if myFuture.isCompleted {              ...     }
파일을 읽어 주세요 라고 (논블럭) 으로 일을 시키고나서, 자신의 일은 안하고 100초에 한번씩 계속 눈치보면서 확인함 (동기)
물론 실제 저런식으로 무식하게 코딩을 하진 않는다. 
 
// 실제로는 아래처럼 onComplete 에 콜백을 등록해 준다.   
// 더 콜백들을 편하게 조작하기 위해서, 많은 언어에서 지원하는 async/await 또한 지원한다.  
val file = Future { Source.fromFile(".gitignore-SAMPLE").getLines.mkString("\n") }  
file onComplete { 
	case Success(text) => log(text)     
	case Failure(t) => log(s"Failed due to $t")  
 }  이렇게 사용하면 논블럭/비동기라 할만하다.
 

블럭/비동기  - JAVA (NIO)

Selector selector = Selector.open();    
ServerSocketChannel mySocket = ServerSocketChannel.open(); 
InetSocketAddress myAddr = new InetSocketAddress("localhost", 1111); 
mySocket.bind(myAddr);  
mySocket.configureBlocking(false);  
int ops = mySocket.validOps(); 
SelectionKey selectKy = mySocket.register(selector, ops, null);
 while (true) {      
 	selector.select();     
 	Set<SelectionKey> myKeys = selector.selectedKeys();     
 	Iterator<SelectionKey> myIterator = myKeys.iterator();      
 	while (myIterator.hasNext()) {        
 		SelectionKey myKey = myIterator.next();          
 		if (myKey.isReadable()) {                         
 		SocketChannel myClient = (SocketChannel) myKey.channel();             
 		ByteBuffer myBuffer = ByteBuffer.allocate(256);             
		 myClient.read(myBuffer);            
		 String result = new String(myBuffer.array()).trim();        
	 }         
 	crunchifyIterator.remove();     
 } 
 }

 

파일(or 소켓입력)을 읽어주세요 라고 부탁하고 나서, 자신의 일을 하다가 파일이 읽혀졌는지 selector.select() 를 통해서 무한 대기 즉 우체국에 전화 걸어서 접수원이 기다리라고 해서 대기중 (블럭).
 
selector.select() 가 리턴을 함 (접수원이 짐이 가득 실어 졌다고 말함) 
그러면 이제 우체국에가서 짐을 실음.(사실 짐을 싣는 과정도 블러킹이지만 멀티쓰레드를 이용해서 병목을 줄일 순 있음)  짐을 싣는 도중에 우체국은 다른 사람 짐을 처리하기 위해 자기 할 일 함.(비동기) 

 

논블럭/비동기 - C++ (IOCP)

while (! isStop())   {     
	위의 이야기에서는 한명의 트럭기사가 처리했지만, 4명의 오너배달부들이 트럭을 감시 할 수도 있다.      
	그럼 한명이 병목되더라도 나머지 기사들이 들어온 짐들을 처리 할 수 있을 것이다.     
	if (GetQueuedCompletionStatus(m_hIOCP, &dwTrans, &pKey, (LPOVERLAPPED*)&pOV, 64))    
	{         
		if (pOV)         
		{             
			MySession* sess = pOV->m_sess;                 ....             
			if (pOV == &sess->m_recv1) //트럭1(버퍼1)을 다 실었다고 우체국으로 부터 연락옴             
			{                
				bool error = false;                
				sess->m_recv1.m_size = dwTrans; // 받은 데이터 사이즈                  
				sess->m_recv2.Reset(); //recv1 버퍼를 처리할것이고, recv2 버퍼는 OS한테 넘긴다.               
				//트럭을 가져온다(기술적으로는 os버퍼에서 응용버퍼로 데이터를 이동한다)                   
				if (! ReadFile((HANDLE)sess->m_sock, (LPVOID)sess->m_recv2.m_data.c_str(), (DWORD)sess->m_recv2.m_data.size(), &dwTrans, &sess->m_recv2))                 
				{                     
					if (GetLastError() != ERROR_IO_PENDING)                         
					error = true;                
				}                  
			OnData(*sess, sess->m_recv1);// 트럭에 실린 짐을 처리하기 시작한다.                 
		if (error)                    
			OnClose(*sess);             
		}             
		else if (pOV == &sess->m_recv2)             
		{                 
				//우체국에서 트럭2에 짐을 다 실었을 경우에 처리한다.                                 
				//트럭만 바뀌었을 뿐이지 위와 동일             
		}         
		}     
	} 
}    
bool MyServer::onData(MySession& sess, MyPacket& data)
{     
	std::string cmd, dat;    
	while (recvPacket(sess, cmd, dat))    
	{        
		//우체국으로 부터 받은 트럭으로 부터 배달을 시작함                
		//여기서 멀티쓰레드 사용하면 (알바배달부를 더 고용하면) 더 효율적이게 됨                
		//물론 너무 많이 만들어도 곤란하다.          
	}    
	return false;
}  
// 이 코드는 트럭에 실린 짐을 확인하는 코드이다.  
// 즉 트럭에 실린 짐이 김연아에게 갈 짐이 맞는지 확인하는 것이다.  
// 트럭에 실린 짐이 김연아에게 갈 짐 중에서 50% 밖에 실리지 않았다면  
// 김연아의 짐이 100% 될 때까지 트럭을 다시 우체국에 보내고 처리하지 않는다.  
// 기술적으로는 "syn:: ~~~ ::end" 까지의 패킷이 완성되야 일 처리를 시작한다는 것이다.  

static bool recvPacket(MySession& sess, std::string& cmd, std::string& data)    
{         
	size_t st = sess.m_recvs.find("syn::");        
	if (st == std::string::npos)            
		return false;          // [패킷작업]  패킷의 끝인 ::end 를 찾는 작업 등           
		if (dt)             
			data = sess.m_recvs.substr(sz+1, dt);          
	return true;     
}
 

* 참고로 자바로도 가능하다 (NIO2) 

이제 정답이 없다는데 이해했으리라 보고 굳이 구분을 하자면 나는 이렇게 구분한다.

@ 먼저 블로킹/논블로킹은 함수 호출에 관해서 국한 한다.
A가 B를 호출 했을 때 B가 A가 원하는 모든 일을 다 마치고 리턴하면 블로킹이고 다 마치기 전에 리턴하면 논블로킹이다. 
함수가 작동하는 시간하고는 무관하다. 1+1만 리턴하는 함수면 엄청 빠르게 리턴 할 지라도  원하는 행위를 다 했기 때문에 블로킹이다.

@ 동기/비동기는 각기 다른 쓰레드/프로세스/서버에서 일어나는 행위에 대한 동시성에 관한 이야기이다.
즉 쓰레드 혹은 프로세스가 분리되서 행위가 일어나는 데, A쓰레드가 B쓰레드의 결과를 계속 대기하고 있으면 동기이다. 
A(쓰레드,프로세스, 서버)가 자신의 일을 하다가 B의 결과를 이벤트로 받아서 처리하면 비동기이다. 

이런 구분방법은 거의 대부분의 경우에서 의사소통을 하기에 매우 분명하며 적절하다고 생각한다.


위의 글은 아래에도 실려있으며 Q/A 가 추가되어 있으니 참고하십시요. https://okky.kr/article/442803


 (A,B) -> B 보다 A -> (B->B) 가 더 좋다?


사실 명령형 파라다임에 적폐인 나로써는, 함수형 파라다임의 체득이란 살아 생전 가능할까 싶다. ㅎㅎ 특히 함수형 파라다임을 체득(머리로 이해가 아닌) 하기 위해서 제일 먼저 만나는 관문을 "커링"이라고 보는데...왜 저 짓을 하냐? 저게 진짜 더 좋긴해? 를 넘어서서 저렇게 하는게 너무 당연하게 느껴지는 퀀텀점프의 경험을 하고 싶다.

* 이 글에서는 부분적용함수와의 차이는 무시한다. 부분적용함수도 커링이라고 친다.

함수형 언어와 함수형 프로그래밍이 다르듯이 파이썬이나 자바스크립트로도 충분히 커링이 가능하다.

// 파이썬
def curry(func, var):
y = var
def f(x):
return func(x, y)
return f

커링을 사용한것 과 안 한 소스부터 일단 보면. (포인트의 이동과 회전에 관한 함수로 이루어졌다)

C 소스 (커링 사용안함) 

typedef point (*converter_t) (point);

point trans(double dx, double dy, point pt) {
point result = pt;
result.x += dx;
result.y += dy;
return result;
}

// 원래 sin,cos 이용해야하지만 그냥 이렇다고 치자.
point rotate(double theta, point pt) {
point result = {0,0}
result.x = pt.x + theta
result.y = pt.y + theta
return result
}
point trans_by_config (config_t config, point pt){
return trans(config.x, config.y, point pt);
}

point rotate_by_config (config_t config, point pt){
return rotate(config.theta, point pt);
}

point convert_by_config(config_t config, point pt){
return trans_by_config(config, rotate_by_config(config, pt));
}
void map_to_points(converter_t conv, size_t n, point * in, point * out) {
unsigned int i = 0;
for (i = 0 ; i < n ; i++) out[i] = conv(in[i]);
}

Javascript 소스 (커링 사용)  

function compose(f, g) { return function(x) { return f(g(x))}}

var trans = function(dx,dy) {
return function(point) {
var result = clone(point)
result.x += dx;
result.y += dy;
return result;
}
}

var transByConfig = function(config){
return trans(config.x , config.y);
}

//rotate 생략

var convertByConfig = function(config) {
return compose(transByConfig(config), rotateByConfig(config));
}

var converted_rect = unit_rect.map(convertByConfig(config));

하스켈 소스 (커링 사용, 타입이 맞아야 한다. 강력한 제한을 둠으로써 완전하게 한다) 

type Coord = (Double, Double)

data Config = Config { rotAt :: Coord
, theta :: Double
, ofs :: (Double, Double)
}

type CoordConverter = Coord -> Coord

trans :: (Double, Double) -> CoordConverter
trans (dx, dy) = \(x,y) -> (x+dx, y+dy)

rotate :: Double -> CoordConverter
rotate t = \(x, y) -> ( x + t, y + t)

transByConfig :: Config -> CoordConverter
transByConfig config = trans (ofs config)

rotateByConfig :: Config -> CoordConverter
rotateByConfig config = postTrans . rotate (theta config) . preTrans where
rotateAt = rotAt config
preTrans = trans (rotate pi $ rotateAt)
postTrans = trans rotateAt

convertByConfig :: Config -> CoordConverter
convertByConfig config = transByConfig config . rotateByConfig config
main :: IO ()

main = do
let config = Config { rotAt = (0.5,0.5), theta = pi / 4, ofs = (-0.5, -0.5) }
let unitRect = [(0,0),(0,1),(1,1),(1,0)]
let convertedRect = map (convertByConfig config) unitRect

"하스켈로 배우는 함수형 프로그래밍"이라는 책에서 발췌한 코드인데, 해당 책에는 이렇게 쓰여져 있다.
"무엇인가와 좌표를 받아서 좌표를 반환하는 것보다, 무엇인가를 받아서 좌표를 받아 좌표를 반환하는 함수를 반환하는 것이 보다 일반적이고 간단한 조합 방법을 도입할 수 있게 된다" 라고 나온다. 

?? 그냥 위의 C 코드에서 함수포인터 만들때 conifg 매개변수도 시그니처로 넣어주면 되는 거 아냐? 라고 반발심도 생기며, 그냥 함수 만들면 되지, 굳이 실행 시점에 만들어지는 함수가 무슨 의미가 있는지, 뭐가 그렇게 좋은지에 대한 "아~~~하" 체험을 하기가 힘들다. 그냥 " 아 그렇군요" 정도는 가능하지만..

대부분의 커링에 관한 블로그에서도 인자수를 줄여주는 마법(?)을 보여주며, 커링 좋지? 라고 말하고 끝난다. 
이건 그냥 그 사람이 커링이 먼지 안 것이지, 그게 정말 좋은지에 대한 설명으로는 부족하다. 

이럴 때~~~

하스켈을 해 봐야 한다. 명령형 언어의 경우 그냥 함수를 몇개 더 만들면 된다. add2 함수 까짓거 만들어도 되지만 그건 추상이 아니다. 그것은 구체적이다. 따라서 커리 만드는데 행사코드가 덕지덕지 붙는다면 그냥 add2, add3 만드는게 낫다.즉 애초에 그런언어로 커리를 공부하면 맘에 와닿을리 없다.

add 에 add2,add3 등의 함수들을 직접만드는 것은 재활용이 아니다. 함수 시그니처가 재각각인 것들을 합성하는 것은 불가능하다. 함수를 합성 하기 위해서는 매개변수가 작고 타입이 같을 수록 좋다. 즉 x -> x 인 함수들 끼리는 합성하기가 너무 좋을 것이다. 존재하는 함수를 최대한 재활용하는것이 함수형이다. 하스켈에서는 아예 매개변수를 1개로 강제하였다. 즉 하스켈에서는 함수가 아예 커링화 되어있다. 하스켈은 2개의 입력값을 받는 함수가 없으며 만들 수도 없다. 함수합성에 대한 강력한 의지가 보인다. (콜스택에 매개변수를 항상 넘겨서 호출하는 것보다, 미리 내부에 정해져있으면 효율적이기도 할 것이다)

let add x y = x + y

이러한 함수도 2개의 x y 를 받는 함수가 아니다. 따라서 아래와 같은 식이 개발자의 추가 노력없이 가능하다.

let add x y = x + y
map (add 1) [1,2,3,4]

 x 를 1로 먼저 입력되어서 만들어진 함수 add y = 1 + y 를 통해 리스트가 입력되어진다.

결국 함수를 재활용하는 함수 합성을 많이 해 봐야 합수합성의 기본도구인 커링,부분함수가 납득(체득)이 되는것 같다.. 이 글을 썼다고 혹은 읽는다고 체득이 되지 않을 것이다. 백날 "자바/파이썬/자바스크립트/스칼라를 통한 함수형 프로그래밍" 류의 책을 읽는 것 보다, 가장 빠른 길은 하스켈를 공부하고, 하스켈 코드를 짜 보는 것 같다. 프로젝트를 하나 해보면 더 좋고~


C / C++ 
:  극한의 상황에서도 돌아가야해. 메모리관리,쓰레드관리,리소스관리를 마이크로 컨트롤하여 솔루션을 만드는게 진정한 개발자라고 할 수 있지
자바 : C++ 은 반쪽짜리 객체지향에다가 오류의 대부분이 포인터 관련인 어처구니 없는 적폐의 언어. 보통 사람의 능력으로는 항상 문제가 발생 할 소지가 많은 언어. 이제 누구나 개발하는 시대이지. 기본적인 것은 플랫폼에서 해주고, 대규모어플리케이션도 편하면서도 강력하게 만들게 해주는 생산성 높은 자바. 대세의 언어가 된데에는 이유가 있는법
파이썬: 얘들아 쉽고 빠르게 개발하는 시대가 왔어. 니들이 진짜 만들고 싶은게 뭐야? 그거에 집중하라고. 초딩들처럼 유치하게 로우레벨 가지고 투닥대지말고~
하스켈,스칼라: C++/JAVA는 그냥 그저그런 지적능력을 가진 애들이나 하는 대중적이고 비효율적인 방식. 지능수준이 높다면 완전한 솔루션을 만들 수 있는 함수형을 선택하는 것은 진리.

라고 주장하는 사람들이 틀렸다고 말하지 않겠습니다. 그냥 자신의 생각일 뿐이니 (과격하지만) 그려려니 하고 싶습니다.  모든 사람이 같은 생각을 하는게 더 무서우니까요.



"리소스,속도 관리 시대"  ->  "생산성, 대중성,적합성 시대" -> "끌리는 것을 선택하는 시대" 


그래도 전체적으로는 언어에 대한 강박관념이 점점 옅어지고 있는거 같습니다."리소스와 속도를 짜내기 위한 언어" 선택의 시대에서  "생산성,대중성,적합성의 시대"를 지나서  "그냥 끌리는 언어를 선택. 오~ 이거 쿨한데" 하는 시대로 이제 접어 들지 않았나 생각합니다. (마이크로 서비스가 한몫을 하며...음 당연히 예외도 있을 겁니다)  "생산성의 시대" 에 진입 하였을 당시에 " 직접 모든것을 다뤄야지. 너 VM에서 하는 것을 어떻게 믿냐?" 라던 사람들이 이제 대부분 찌그러졌습니다. "끌리는 언어를 선택" 하는 시대로 진입하는 현재 "니 맘대로 하냐? 적합해그냥 대세를 따르는건 어때?  "라며 딴지 거는 사람들이 아직은 많을 겁니다만 앞으로는 이것도 점차 희미해 지리라 봅니다.

즉 스칼라,클로저,러스트,Golang,얼랭,코틀린을 개인의 흥미로 선택해도 이해해 주는 시대. 그 아름다운 시대에 접어 들었습니다. 

기대합니다.


소프웨어 엔니지어링에서 가장 교조주의적인 덕목은 인터페이스를 위시한 유연성,확장성이 아닐까 싶다. (추가적으로 제네릭과 폴리모피즘의 환상 떡칠은 가독성을 최악으로 치닫게 만든다.) 그게 절실히 필요한 코드는 5%나 될까 싶은데 주로 공용 라이브러리겠지.나머지 95%의 코드를 만드는 사람들이 모든 책에 적혀져있는 "확장가능하게 만들어야한다" 를 맹목적으로 받아드리고 있는거 같기도 하며,교조적인 덕목에 따라 혹시 자신은 유연하게 만들지 않았는지에 대한 죄책감이 남아 있을지도 모르겠다. 물론 그 정도 고민을 하는 사람이라면 그것만으로도 훌륭하다고 볼 수 있겠다. 까놓고 말해보자. 자신이 만든 클래스가 100개 있다면, 그 중에 1년 후에 형제 클래스를 만들어야 했던 경우가 얼마나 되었는지. 내 경우는 하나 밖에 없다. 솔직히 말하면 오히려 안쓰던 클래스를 줄였다.일반 응용프로그래밍 작성시 확장성 있게 만들지 않아서 고통받는 코드는 거의 별로 없다. 성능문제와 마찬가지로 확장성이 필요하면 그때 가서 고쳐도 충분한 코드(팀내에서 조율 가능한)가 훨씬 더 많다.짐을 내려 놓아도 된다.


*

특히 대형 솔루션을 만들지 않는 마이크로 서비스 형태의 스타트업이라면 철저하게 빠른 구현 위주로 가야 하며, 뜯어 고치는거 & 더 좋게 새로 만드는 것은 눈감고도 기민하게 해야 한다.즉 비지니스 로직(예를들어 MVC 경우 Controlller 단, Service 단) 따위는 유연성을 따지는 것은 오바고, 다양한 외부인터페이싱이 요구될 가능성이 있는 DB 접속 (예를들어 MVC 경우 Repository단) 같은 경우에도 PostgreSQL 을 사용하다가 InfluxDB 를 추가시 기존에 공통 인터페이스가 없더라도 후딱 만들어 넣어야 한다는 야그~

'소프트웨어 사색' 카테고리의 다른 글

왜 커링 (currying) 이 더 좋아?  (1) 2018.03.28
언어 선택의 기준  (0) 2018.03.20
쓰레스세이프하다? 란 과연 무엇인가?  (0) 2018.03.02
블럭체인/비트코인  (1) 2018.01.31
소프트웨어는 유기물  (0) 2018.01.15


쓰레스세이프하다? 란 과연 무엇인가? 
모든것이 불변이면? 동기화객체로 공유변수가 감싸져 있으면? TLS,STM,Actor,CSP 를 사용하면? 

땡~~~!

쓰레드로 경쟁으로 인해서 솔루션이 원하는대로 동작하지 않는다면 모두 쓰레드 세이프하지 않는 것이다.
좁은 의미로 단어를 사용하다 보면 그게 넓은 범위로 벌어진다고 착각하기 쉽다. 
객체 및 내부 변수를 불변으로 만들어 두었다고 쓰레드세이프하다고 착각하지말자.
그것을 어떻게 사용하냐에 따라서 쓰레드들은 당신이 원하는대로 동작하지 않은 결과를 내놓을 것이다.

'소프트웨어 사색' 카테고리의 다른 글

언어 선택의 기준  (0) 2018.03.20
인터페이스라는 사치  (0) 2018.03.09
블럭체인/비트코인  (1) 2018.01.31
소프트웨어는 유기물  (0) 2018.01.15
굿바이~ 옵저버 패턴 and FRP  (0) 2017.08.17


한글,한국어 공부했다고 멋진 소설이 안나오듯이, 해쉬,비대칭암호화,링크드리스트 ,p2p를 학생때 이해했다고 블록체인/비트코인등의 멋진 알고리즘,아이디어를 만틀 수 있는 것은 아니다. "천지차이"

컴퓨팅파워, (특히) 저장용량, 네트워크의 엄청난 발전으로 그 동안 알고있었던 기술,아이디어를 확장,발전시킨 빅데이터,딥러닝,사물인터넷(특히 스마트폰 같은 개인화 네트워크 덕분) 등은 모두 "블럭체인" 의 탄생과 궤를 같이 하고 있다고 볼 수 있다.



"규제 받지 않는 인간(민간)의 무한한 탐욕"


신뢰의 사회적 비용을 낮추는 탈중앙화 때문에 역설적이게도 민간 자본 괴물이 나타날 수도 있을 거란 우려가 드는 것은 왜 일까.. 즉 사회적 비용은 낮추지만, 균형은 더욱 무너트리는..앞으로 누군가 (or 단체) 는 이 탐욕을 위해서 계속 시도 할 것인데, 누군가(or 단체)는 이 탐욕에 뒤떨어져서 손해를 보는 세상이 온다면 참 마음 아플것 같다.


요즘 방송에 나오는 유시민 작가님의 말하는 것에서의 몇가지 기술 스펙적 오류 (있다) 는 저 주장에 크게 흠이 잡히지 않는다. 유시민 작가는 컴공/역사/경제/인문학적인 기반하에 주장을 펼치기 때문이기 때문이다. 따라서 " 좀 더 제대로 공부했으면 " 라고 유시민작가에 비판을 하는 사람들은 공염불에 그칠 것이다. 저런 논쟁은 좀 더 제대로 비트코인 스펙을 공부한 사람이라도 찬/반, 흑/백을 알 수 있는 문제가 아니기 때문이다. 컴퓨터 공학적으로의 불록체인 이해와 인문학/경제학 적으로의 블록체인 이해 양쪽이 합쳐져야만 하는 문제인데다가 양쪽을 합쳐서 잘 이해하고 있다고 해도 정답을 맞출 수 없는 문제이기도 하기 때문이다. 따라서 유시민 작가의 논리/의견을 반대 하는 사람들은 좀 비겁해 보이는 "좀 더 제대로 공부 했으면" 을 대신해서 그냥 자신들의 논리로 반박하는게 좋지 않을까 한다.

이제 논점을 바꾸어 보자.

지금 당장은 저런 투기성 문제에 힘을 실어줘서, 한 단락은 정리 하는게 좋지 않을까 한다. 지금 현실적으로 큰 문제가 있는데 토론의 방향이 그냥 지나쳐 줄 수는 없을 것이다. (저런 토론 자체를 자신의 코인가격이 떨어 질까봐 싫어하는 일부 투기 코인꾼들에겐 미안하지만) 투기성 및 기술적 한계등 뻔하게 있는 문제에 대해 빨리 썩은 가지는 처 내고 보다 발전적인 논의로 나아가길 기대한다. 지금 당장 혹은 몇년 내로 블록체인의 응용 제품들이 나오고 활성화 되는 시대가 되지 않는다고 생각 된다. 그 과정에서의 이런 잡음은 당연히 필요하다고 보며, 너무 서두를 필요도 없으며 차분히 준비하면 된다. 유시민 작가님과 지금 상황에서 대적(?) 하기 위해서는 그 논점에서는 님 말씀이 옳습니다.라고 인정하자. 그리고 이제 그럼 비트코인의 한계를 벗어난 다른 시스템들의 효용성과 퍼블릭 or 국가적 프라이빗 블록체인 (유시민 작가 의견으로는 이게 퍼블릭. 공공재니깐.) 블록체인과 사물인터넷과의 접점 및 그런 탈중앙기반 신뢰 시스템이 4차혁명에서의 어떤 역할을 할 수 있는지, 국가 경쟁력과는 어떤 상관관계가 있을 것인가에 대한 심도있는 토론을 해 가도록 하는 방향으로 가닥을 잡아야 할 것이다.


개인적으로 퍼블릭 or 프라이빗 블록체인과 사물인터넷이 결합된 세상을 꿈꿔보는 소프트웨어 개발자로써 지금 당장은 기술에 대해서는 설레지만 사회에 초래되는 결과에 대해서는 부정적인 입장이다. 신뢰 비용은 낮추되, 개인간의 균형은 기울어지게 될 까봐 우려된다. 많은 지식 공유/토론이 필요하다. 당장은 퍼블릭 블록체인에는 폐해 및 구현 상 문제점 이 많아 보이고 그렇다고 프라이빗으로 하자니 과연 그게 블록체인 철학에 부합하는 것일까? 하는 의문도 있다.





소프트웨어 개발은 집을 짓는것 보다는 정원을 돌보는 일이라고 합니다. ㅡ실용주의 프로그래머 인용


살아 숨쉬는 녀석들을 꾸준히 관리 해 줘야 한 다는 의미겠지요. 더 이쁘고 건강 하게...
가꿀수록 좋아지는 소프트웨어 개발은 정원처럼 한번 만들고 나서 내버려두는것이 아니라, 계속 관심을 쏟는게 필요하며, 그런 의미에서 테스팅 코드, 특히 리팩토링등은 당연해 집니다.

그런것을 무시 하는 관리자들이 많은데요.

개발자들이 코드를 보살펴 줄 시간이 필요 하다고 하면, 말 좀 들으십시요. 다른 정원 만들라고 하지 마시고....모든 정원에 잡초와 폐자재와 쓰레기가 쌓이고 오염되는것을 원치 않는다면 말이지요.

그런의미에서, 개발자들이 어떻게 보살펴 줬는지 질문하고,  확인하며, 어드바이스를 할 줄 모르는 사람이라면 개발자들을 관리하거나 리딩하지마세요. CEO 들은 그런 사람에게 그런 일을 시키지도 마시고~ 약속

참고로 정원을 또 만들때보다 가꿀 수록 실력이 증가합니다. 코드 품질도 좋아지고 직원들 실력도 늘고 굿~


ps. 

6개월 짜리일을 3개월에 하라는 갑과 먹튀 SI 전문가들에게도 꽃이 피길..




문제 공유

우리는 오랫 동안 상호작용 되는 많은 부분에 있어서 옵저버패턴을 당연하듯 활용해 왔지만,

옵저버(관찰자, 소비자, 리스너) 패턴을 사용하다보면 경험 많은 개발자라면 누구나 "아 이거 먼가 깨름칙 한데" 라는 경험을 해보았을 것이다. 나 같은 평범한 개발자의 경우 그런 깨름칙한 냄새를 맡고서도, "내가 모자라서 그렇지 뭐" 자책을 하거나,  "여기서 어떻게 더 잘 고칠수 있지? 옵저버패턴은 Gof 패턴 중 하나이며 훌륭한것이니 더 나은것은 없을 거야" 라고 이른 만족을 하거나, "그냥 잘 굴러가는 거 같아 보이니, 냅두자", "나는 코드를 잘 이해하고 있어, 다른 신참이나 이해 부족한 개발자 네 탓" 이 라고 기술 부채를 남기며 자기 최면을 건다든지 할 것이다. 

하지만 역시 구루님들은 달랐다. 옵저버패턴의 문제점을 요목조목 따져가며 샅샅히 지적을 했으며, 그것에 대한 해결책까지 나왔다.  따라서 이젠 거인의 어깨에 올라 타서 그것에 관한 이해하고, 잘 만들어진 라이브러리를 사용하면 된다. 그 전반적인 내용에 대해서 설명 해 볼 예정이지만 모든 것을 쉽게 풀어서 적을 수는 없기에,  중간중간 이해 하기 힘든 구멍들은 각자 수고스럽지만 찾아보거나, 깊은 사색을 해야 할 것이다. 

옵저버 패턴

어떤 상태를 관리하는 Subject 객체(상태머신,생산자,Observable)가 있고 , 이 객체에서 일어나는 상태 변화에 따라서 해당 이벤트를 받길 원하는 옵저버(관찰자,소비자)들은 매니져 객체에 자신을 등록 한다.

그 후에, Subject 객체에서 어떤 상태가 변경 되었을 때 , 자신에게 등록된 옵저버들에게 이벤트를 notify 해주는게 옵저버 패턴의 주요 골자이며, Subject 객체은 관찰자들에 대해서 유연하게 커플링 되어 있게 된다. 즉 매니저 객체가 소비자들에 대해서 정확한 정보를 알 필요가 없이, 소비자들이 알아서 Subject 객체가 선언한 인터페이스를 따라주고, 등록해주면 되므로, 매니저 객체는 독립적인 컴포넌트가 될 수 있는 여지가 생기는데 옵저버패턴은 그런 유연성이라는 장점을 내세우는 패턴이라고 할 수 있다.

자~ 그럼 옵저버패턴은 어디서 사용 될 까?

글쓴이 본인은 주로 그래픽스/편집기 솔루션을 개발한 경험이 많기 때문에 관련하여 설명 해본다. (이러한 곳에서 정말 많이 사용된다) 

각종 도형의 리스트가 나열 되어 있는 리스트 박스가 있다. 우리는 그 리스트에 추가,삭제를 할 수 있는데, 도형 하나를 추가하면 (상태를 변경하면) 그 소식을 리스트의 데이터와 밀접한 관계를 가지는 다른 컴포넌트(객체) 들에게도 연락을 해줘야 한다. 만약 삼각형3을 추가해줬다면 아래와 같은 변경이 전파되어야 한다.

- 마우스 커서는 삼각형3에 해당하는 커서모양으로 변경
- 상태바에는 삼각형3가 추가되었다고 글씨가 추가
- 뷰화면에는 삼각형3에 해당하는 도형이 그려짐.

GUI 부분에서는 이것 말고도 매우 다양하게 사용되는데 다른 예는 마우스 버튼이 Subject 가 되며, 버튼이 클릭되는 상태변경 (좌클릭,우클릭,더블클릭등) 에 따라서 옵저버들이 고지 받아서 행동(선 그리기)하게 될 것이다. 

웹이나 통신 개발에서의 예를 들면 아래 처럼 구성이 될 것이다.

MVC 패턴에서, M (model) 은 subject 역할을 맡고, V(view) 는 Observer 역할을 맡는 것도 유추 할 수 있다. 

옵저버 패턴의 특징 

디커플링 : 상태머신이 자신이 호출해 줘야 할 객체들을 모두 강하게 내부 변수로 가지고 있게 되면, 상태머신 자체를 다른 곳에서 재활용하기 힘들게 된다.

제어역전 : 옵저버 패턴은 전형적인 제어 역전 구조로 (사실 제어역전이란 말은 모호하기 때문에 그냥 DI 로 사용하는게 나음) 옵저버들이 상태머신을 계속 polling 하거나, 상태머신을 호출하는 것이아니라,  상태머신에 자신의 레퍼런스를 넘겨서 (addObserver , addListener , subscribe ) 상태머신에 의해 콜백되게 만든다.

옵저버 패턴의 문제 

1. 예측 불가능한 순서

Subject (상태머신) 자체내에서는 옵저버들을 보통 리스트를 순회하며 Notify 를 해주게 되지만, 옵저버들의 추가,삭제가 자유로이 이루어지고 있고, 개별 옵저버 입장에서는 자신이 호출되는 순서에 대해서 알 수가 없다. 따라서 상태변경에 따른 행위를 할 때 , 자신의 순서 앞에서 어떤 다른 옵저버가 어떤 행위를 했는지를 미리 알 수가 없게 된다. 제어권을 Subject 로 제어역전을 시켜 준 결과 이렇게 되버리는데, 이것을 해결 하기 위해서는 옵저버들 전체를 일종의 트랜잭션으로 감싸서 트랙잭션이 끝날 때 어떤 행위를 하게 끔 하는 수 밖에 없지만, 코드복잡도가 상당히 올라가게 마련이다. 

2. 첫번째 이벤트 소실

Subject(상태머신) 에 DI 를 해주는 시점이, Subject 에서 첫번째 이벤트(상태변경)가 발생하는 시점 보다 늦게 될 수가 있다. 예를들어 클라이언트가 접속되었다는 이벤트를 통지 받지 못한다면, 클라이언트와 상호통신하는 옵저버는 무용지물이 될 것이다.

3. 지저분한 상태

Subject(상태머신) 은 위에서 보았다시피, 계속 변경되기 때문에 그로 인한 부수효과(side-effects) 가 발생하기 마련이다. 상태가 1~2개라면 모르겠으나, 상태가 만약 5개이상을 가지고 있다고 하자. 그 상태를 변경하는 이벤트들의 종류가 10가지 라고하면, 50개의 조합이 생겨난다. 그런 조합에 의해 옵저버들이 호출 되었는데, 먼가가 작동을 안하는 버그가 생겼을 때 , 현재 상태가 올바른 상태인지에 대한 디버깅이 힘들어지기 마련이다. 

4. 캡슐화 문제

옵저버패턴은 캡슐화를 종종 깨버리는데,  상태머신의 변경에 따라서 a 옵저버가 mylist 라는 변수를 초기화 시키고, b 옵저버는 mylist 라는 변수를 사용하게 되는 경우가 많이 있다.

5. 스레드 안전 문제

가장 곤혹스러운 문제이다. 일단 Subject (상태머신) 내에서도 락이나 경쟁관계를 해소해야하지만, 그것 보다 더 큰 문제는 각각의 옵저버들과 그 옵저버들이 호출하는 함수체인 속에서 어떤 락을 잡고 있는지 알 수 없게 되는 경향이 있다. 즉 A 옵저버가 a 락을 잡고 b 락을 잡으려고 하지만, B 옵저버가 이미 b락을 잡고 있다면 (a락을 잡아야 b락을 풀어준다면) 터지는거다. 역시 상태를 가지고있는 모든 OOP 프로그래밍에서의 쓰레드 사용은 큰 문제거리가 될 수 밖에 없을 것이다. 더군다나 옵저버패턴처럼 제어권이 역전 된 상태이면 더더욱~

6. 콜백 누수

가장 옵저버(리스너)를 등록 시킨 후에, 쓸모가 없어 졌을때 removeObserver, removeListener, dipose 등을 호출하는 것을 잊어 버렸다고 하자. 앞으로 상태가 변경 될 때 마다 쓸때없는 CPU 사이클만 날려버릴 것이다. 
옵저버 패턴이 생산자와 소비자의 제어 관계를 자연스럽게 역전 시켜서 생산자가 소비자에게 의존 하지 못하게 만드는 것이지만, 생산자의 실수를 소비자는 알 수가 없게 된다. 이상적이라면 이 관계를 다시 역전 시켜야 한다. 

7. 의도치 않은 재귀 

실제 현업에서 복잡한 솔루션을 짤 때, 이 문제도 쓰레드 문제와 같이 가장 크게 다가오곤 한다. 예를들어 커맨드 패턴에서 execute 가 발생해서 -> 상태머신(S) 의 상태를 변경하면 -> 옵저버 A가 호출되고 -> 옵저버 B가 호출되고 -> ... -> 옵저버A 는 어떤 다른 함수를 호출하고 그 함수는 -> 무엇인가를 하고 -> 여기서 끝나야 하지만 ->  마지막 함수는 다시 상태머신(S) 를 변경한다. 

바보라고 말 할 수 있겠지만, 정말 복잡한 수백만 라인의 코드에서는 종종 일어나는 일이다. 자신이 코드의 모든 곳을 속속들이 알지 못하는 신참일 경우, 일 부분에 대해서만 작업을 하게 되는데 자신의 작업이 가져 올 여파까지는 미리 알 수가 없는 상황인 경우가 발생한다.

8. 기타등등

Composability, SOC , Scalabilty, Abstraction, Resource management, Semantic distance 등이 있으며. 아래 레퍼런스 중 첫번째 마틴오더스키의 논문에 짧은 코멘트가 적혀져 있다.

자! 이 모든 문제를 해결 해야 할 때가 왔다. FRP(Functional Reactive Programming) 이 그것을 해준다.
다음 편에서는 아래의 주제로 이것에 대한 해결 방안들을 알아 보자.


함수적 반응형 프로그래밍 (FRP) 

먼저 리액티브라는 단어에는 다양한 의미가 있지만 가장 개발자로써 와닿을 수 있는 예시를 하나 보자.
{
  a();
  b():
  c();
}

절차지향에 익숙한 우리는 위의 함수들이 순서대로 발생할 것이라는 것을 염두해 두코 코딩을 하게 된다.

즉 c() 함수는 a() 함수와 b() 함수가 무엇인가 처리를 한 후에 그것을 가지고 처리한다는 건데, 물론 순서가 중요하지 않을 때 도 있다. 이때 나중에 참여한 개발자가 저것을 다 파악하기란 힘들 것이다. (물론 소스가 복잡해 진 다면 말이죠) 또한 옵저버패턴이 사용된다면 그 순서란 더 감춰지기 마련이니 파악하기 힘들어 질 것이다.

의존 관계 와 순서는 이렇게 우리가 짜왔던 절차지향,객체지향에서는 혼동을 주게 되는데 최근에 코어를 더 적극적으로 활용해야하는 시대에 와서 멀티쓰레드라는 동시성을 다루는 부분에 있어서 더더욱 순서와 의존관계를 파악하기 힘들어 졌다.

FRP 와 Rx 의 반응형이라는 말에 뒤에는 이런 순서&의존관계를 명확하게 인지 시키준다는 의미도 포함되어 있으며 앞으로 FRP 와 Rx 를 공부하는데 있어서 이 점을 머리속에 넣어 두면 좋을 거 같다.

첨가)  FRP와 Rx 시리즈들과는 다르다. Sodium FRP 저자가 작성한 내용을 참고하면 



작성중.... 



레퍼런스:

https://infoscience.epfl.ch/record/148043/files/DeprecatingObserversTR2010.pdf
Functional Reactive Programming 
https://www.scala-lang.org/
https://github.com/ReactiveX/RxJava
https://github.com/SodiumFRP/sodium
https://github.com/ReactiveX/RxPY

+ Recent posts