1. 멀티스레드로 작성하면 안되는(또는 곤란한) 알고리즘 유형들 :
(1). 정렬 알고리즘 :
멀티스레드 입문자에게 가장 매력있어보이는 분야가 아닐까 싶은데 코어를 2개 투입하면 2배, 4개 투입하면 4배, n개 투입하면 n배로 빨라지지 않을까하고 삽질을 해서 '멀티'스레드로 돌아가는 것처럼 보이는 코드를 작성하지만 정작 실행해보니 실행 성능이 코어 투입량 대비 n분의 1로 나오는 경험을 많이 할거라 생각함. 간단하게 설명하면 스레드 동기화 때문에 코어당 성능이 절반으로 줄어들기만 해도 싱글코어보다 이득이 없다는 것.
+ 추가 의견 :
애매모호한 설명을 가지고 있어서 문제가 되었는데 분할 정복 알고리즘 계통은 멀티스레드에 의하여 상당한 성능 향상을 보입니다. 여기서 주장하는 것은 분할 정복이 아닌 복잡한 정렬 알고리즘인 경우 데이터 의존성으로 인해 더 느려질 수 있다는 이야기였습니다.
(2). 특정 데이터에 지나치게 의존하는 코드 :
어떤 스레드가 특정 데이터의 계산을 끝마치기 전까지는 다른 스레드가 동작하지 못한다면 동시에 하나씩만 실행되므로 멀티스레드의 의미가 없어짐. 또는 특정 데이터를 놓고 경쟁하는 경우 뮤텍스나 세마포어로 제어할 수는 있지만 이런 제어 함수의 실행 비용이 저렴한 편은 아니기 때문에 좀 많이 느려짐.
(3). 복잡한 구조를 가지고 여러 스레드에 간섭하는 경우 :
이런걸 의존성 지옥은 데드락이 발생할 가능성이 높고 운 좋게 하나의 문제를 해결하더라도 다른 문제는 이와 관련없는 경우가 많으면 폭탄 해제하듯이 일일히 논리를 검증해야함.
2. 데드락이 발생하는 상황.
(1). 뮤텍스를 걸고 풀지 않는 경우
mutex_lock();
exit();
뮤텍스를 걸어놓고 풀지 않아서 데드락이 발생하는 상황은 많진 않은데 멀티스레드 프로그래밍을 배울때 임계 구역과 같이 뮤텍스를 설명하는 경우가 많아 끝에는 반드시 뮤텍스를 해제하는 것으로 교육받기에 실제로는 문제가 거의 일어나진 않지만 잘못된 실행으로 스레드 자체가 강제종료했을때는 이야기가 달라짐. 그때는 클린업 핸들러에 뮤텍스 해제를 반드시 실행하자. 다만 스레드가 강제종료한 시점에서 이미 프로그램의 정상 실행을 보증하기는 어려우므로 프로그램이 강제종료하는 경우가 많아 왠만히 안전장치를 걸어두지 않는 한(스레드가 강제종료해도 다른 스레드는 영향이 가지 않게 설계한 경우) 보기는 어렵다.
(2). 조건 변수
조건 변수는 어떤 스레드가 주시하고 있는 조건 변수에 시그널이 전달되기 전까지 수면 상태로 들어가는 기능인데 유닉스 신호처럼 명시적으로 받는다고 정의하지 않아도 자동으로 받는 개념이 아니기에 이게 어떻게 보면 데드락의 가장 큰 원인이 될거라 생각함. 예시를 들면 스레드 A가 신호를 기다리고 있다고 가정하고 스레드 B가 신호를 보내고 대기하는데 정작 A는 신호를 못받아서 못깨어나고 B는 신호가 제대로 갔는지 검사하는 로직이 없고 이미 잠들어버려서 영원히 교착 상태에 있는 문제인데. 사실 간단히 해결할 수 있는 문제임.
유닉스 신호는 비동기식 매커니즘이고 신호를 기다린다고 명시하지 않아도 자동으로 잡고 적절한 처리 핸들러를 실행시키는데 반대로 조건 변수와 시그널은 동기식으로 동작하기에 신호 대기중이 아닐때는 신호를 보내도 무시하고 심지어 신호 대기로 전환중일때도(보통 조건 변수를 기다리는 함수를 실행할때) 무시되므로 실행 순서가 잘못되면 그야말로 디버깅 지옥이 일어날 수밖에 없음. 조건 변수를 기다리는 함수는 인자로 뮤텍스를 같이 받기 때문에 완전한 신호 대기 상태에 들어가면 잠금을 일시적으로 풀어주고 신호를 보낼 수 있도록 해야함.
(3). 알 수 없는 원인
대부분 동기화 로직이 잘못되었을때 일어나지만 잠금을 소유한 상태로 특정한 행동을 하면 발생하는 경우도 있으므로 잠금이 필요없을때는 반드시 바로 해제해주고 너무 넓은 범위를 임계 구역으로 설정하지 않는 조치가 필요함. 보통 데드락은 2개 이상의 스레드가 연관되었을때를 말하는 경우가 많지만 지혼자 잠금을 두번 이상 요청해서 일어나는 경우도 많다. 짤막한 팁이지만 뮤텍스는 임계 구역과 전혀 연관성이 없는 다른 스레드에서도 잠글 수 있다. 이는 뮤텍스라는게 임계 구역이라는 것을 프로그램상에서 만들어주는게 아니고 단순히 한 번에 한 놈만 들어가게 만들어주는 기능이므로 메인 스레드가 잠그고 다른 스레드가 대기하는 방식도 유용하게 사용됨.
3. 스레드 강제종료 원인
(1). 스레드에서 잘못된 동작을 했을때
싱글스레드만 쓰는 프로그램과 별 다른건 없고 이건 실수만 안하면 잘 일어나지 않음.
(2). 스레드 매개변수 문제
스레드의 매개변수가 문자 포인터고 이 값이 변경될 여지가 없다면 문제는 없지만 공간 부족등의 이유로 메모리 공간을 재할당한 경우. 메모리 재할당은 반드시 이전과 똑같은 메모리 주소를 반환한다는 보장이 없기 때문에 인자로 전달된 주소값은 잘못되게 되고 여기에 접근하면 메모리 오류, 이미 할당되어 있는 공간인 경우 버그가 발생하게 됨. 이 문제도 간단하게 해결한다면 매개변수를 위한 구조체를 만들고 이 안에다가 필요한 값들을 저장한 다음 구조체의 주소를 보내면 해결됨. 가변 인자를 받는다고 해도 구조체의 주소는 절대로 변하지 않기 때문에 가변 인자의 주소가 바뀌든 말든 문제는 없지만 성능 향상과 가독성을 위해 구조체를 지역 변수로 복사하면 여전히 문제가 발생할 수 있음.
(3). 다른 스레드에서 일어난 문제 때문에 같이 동반사망하는 경우
스레드 전용의 유닉스 신호 마스크가 존재하므로 잘 사용하면 되지만 애초에 그런 버그를 만들지 않는게 더 중요하다고 생각함.
4. 멀티스레드로 작성하면 효율적인 프로그램들
(1). 반복적인 작업을 많이하면서 싱글스레드로는 도저히 성능 향상의 여지가 없을때.
유감스럽지만 멀티스레드를 시도하는 대부분의 프로젝트는 이런 상황이 아님. 마인크래프트가 멀티스레드 활용 제대로 못한다고 맨날 까이지만 그 당시에는 싱글 스레드만 돌아도 충분히 빨랐고 지금은 더 빨라서 모드떡칠하거나 무거운 작업 돌리지 않으면 잘만 돌아감.
(2). 운영체제
의외로 간과하는 사실인데 커널도 하나의 거대한 프로그램이고(다만 프로세스가 아닐뿐) 다중 코어를 인식하고 사용할 수 있으면 그 위에서 돌아가는 프로세스는 서로 데이터 연관성이 없는 경우가 많아 성능 향상이 매우 큼. 그런데 커널 수준에서 코어를 다룰려면 쉬운 멀티스레딩을 도와주는 라이브러리같은건 사용하기 어려우므로 직접 하드웨어에 접근해야 한다.
(3). 게임
마인크래프트도 멀티스레드로 최소한 로딩까지는 스레드를 활용하면 좋지 않았을까 생각함... 렌더링은 어쩔 수 없이 OpenGL 한계상 1스레드로 돌아가야겠지만 다른건 멀티로 충분히 잘 돌아갈 수 있지 않을까? 아니면 게임 프레임에 따라 스레드 수를 적절히 조합하는 것도 방법. 4-1번에서 말한게 이건데 게임 프레임이 10프레임씩 나온다는건 싱글스레드로 도저히 성능 향상의 여지가 없다는 조건에 부합하므로 스레드를 더 투입하면 동기화 자원의 소모를 고려해도 더 빨리 작동할 수 있음. 왜냐하면 그정도까지 오면 1스레드로 사용량 100%를 찍고도 느려진다는 거니까. (유감이지만 노치가 똑똑하질 못했다.)
5. 멀티스레드와 유사한 기법들
(1). 다중 프로세스
멀티스레딩이 없던 시절에 유일하게 사용할 수 있는 다중처리 기법이지만 스케줄러의 영향을 상당히 많이 받아서 효율이 좋지 못하지만 이런 용도는 유닉스의 파이프같은 편의성 위주의 기술임. 물론 세마포어도 존재하고 이를 이용하여 다중처리는 가능한데 커널에서 멀티스레딩이 추가된 이후로는 그냥 이러한 기능이 있었다 정도로만 언급되고 자원 공유나 효율성만 생각할때 사용하는 기술.
(2). 코루틴
엄밀히 말하자면 동시에 처리되지 않는다는 점에서 다중처리에 속하지는 않지만 멀티스레드의 초기 목표처럼 편의성이나 응답성으로 소개된 기술이라 그정도로도 큰 의미가 있는 기술. 특히 입출력과 계산은 동시에 하기 어렵지만 코루틴을 사용하면 입출력을 대기하는 시간 동안은 잠깐이라도 계산이 진행되니 멀티 코어처럼 처리량의 근본적인 증가가 아닌 SMT처럼 코어 하나의 효율성을 올리는 기술.
(3). 클러스터링
컴퓨터 여러개를 연결하는 방식이라 응답성은 포기하는 대신 처리량만 높으면 되는 슈퍼컴퓨터에 흔히 쓰임. 다만 슈퍼컴퓨터 구조가 가면 갈수록 복잡해져가므로 클러스터링 한번으로는 오히려 제어하는 컴퓨터에 병목이 올 정도로 복잡해져서 클러스터를 여러 단계로 구성하는 것도 간간히 볼 수 있다. (전체 노드가 1백만개면 이걸 10만개 단위로 나누고 이 그룹별로 제어 컴퓨터를 물린 다음에 이 제어 컴퓨터를 모아서 중앙 컴퓨터에 연결하는 방식으로 사용한다. 노드가 많아질수록 이 단계가 추가되고 응답성은 떨어짐.)
난 멀티스레드 분야 벗어나면 제대로 설명해줄게 없을 정도로 컴퓨터 과학 지식 수준이 너무 낮아서 잘못된 점이나 개소리에 가까운 헛소리가 좀 많긴 한데 그런게 있으면 비추 대신에 지적해주면 정말 진지하게 들어줄 수 있으니 사양말고 다만 비추에는 가까이 가지 말았으면 좋겠어...
정렬은 데이터 많으면 효과보는데
내 예상에 10억개 정도가 아니면 싱글스레드가 더 빠를듯?
10만 정도도 효과 있을껄 내가 대충만든것도 vector iterator 기준 2스레드 100만 소모시간-35% 정도인데 완성도 높으면 더 빠를걸, gpgpu sort도 있다고 하던데
merge?
아니 걍 퀵소트 대충 고쳐서, 퀵소트1사이클 돌때마다 반으로 분할대는거 스레드2개가지고 서로 주고받고, 머지면 omp같은거 대충 동원해도 훨빠를듯
내가 한번 구현해볼께, 근데 스레드 2개 가지고 서로 주고받는다는게 무슨 말을 하는거야
걍 코드보여줄게
https://github.com/lastpenguin/test/blob/master/QuickSort.h
CUDA충인데 보통 GPU는 Radix 정렬인데 부끄럽게도 CPU랑 비교 해본적이 없다 ㅋㅋ.. 지레 짐작은 가는데 해본적이 없는걸 지금 씨부리면 개소리일 뿐이니 나중에 해봐야지 ㅠ 굳이 비교할 필요도 못느낀 변명을 해보자면 실제로 응용할땐 정렬은 파이프라인의 일부분일뿐이고 설사 CPU 정렬이 빠르다고 해도 CPU <-> GPU 메모리 카피 오버헤드 까지 합치면 존나 비싸다는거
구현해봤는데... 이건 내 잘못 인정하고 수정하겠음.
주 석보니 데이터5만개 좀 안될때 앞서넹~
cpu/thread affinity를 조정해서 어느 특정 스레드만 데이터를 접근하게 바꾼다면 그렇게 느리진 않을 거임
두 쓰레드가 공유하는데 값의 변화가 없는 쓰레드면 락 안걸어도 댐?
읽기만하는 스레드랑 수정도 하는 스레드말하는거? 1개 임계영역이 데이터가 여러개고 데이터끼리 밀접한 관계있으면 락 걸어야징, 읽기스레드가 2개 이상이면 rwlock
읽기만 하는 스레드가 그 값이 어떤 특정한 제어 구역을 지나기 전까지는 절대 바뀌지 않는다고 확신할 수 있으면 읽을때 락을 걸을 필요는 없지만 제멋대로 바뀔 수 있다면 락을 거는게 맞지.
마인크래프트는 MS에서 다시 만들어서 뿌렸다
정렬에 멀티스레드가 왜 효과가없어...
코드 돌려보고나서 알았는데 퀵정렬 2스레드로 효과 있었네... 이건 문제 있었다는것을 인정함.
뇌피셜을 사실인것마냥 당당하게 적으니까 읽기도 귀찮다
어디가 뇌피셜인데?
"멀티스레드 입문자에게 가장 매력있어보이는 분야가 아닐까 싶은데" => 글쓴이 본인에게 가장 매력있어보이는 분야여서 시도해 봤는데 그 뒤에 내용은 전부 니가 이상하게 짜서 실패한 내용을 객관적인 척 적어놓은 뇌피셜
죄송한데 멀티스레드 정렬은 한번도 시도해본적이 없읍니다. 그냥 이론적으로 가능할지만 계산만 하다 덮어버린거라... 오늘 올린 코드가 첫번째 멀티스레드 정렬 알고리즘의 구현입니다.
그냥 니가 처음 시도해서 실패해서 멀티스레딩 되는검지 안 되는건지도 모르는데 그걸 정보글 적듯이 거창하게 정렬은 멀티스레딩 성능향상 없다고 사실인양 말하지마라
ROGER WILCO, 내가 올린 최신글보면 잘 작동하는데 클럭으로 측정된거라 2스레드에서 시간 뻥튀기되는데 2로 나누면 대충 1스레드보다 빠른건 맞음.
시간 측정도 못하는 새끼가 쓴글을 읽으려고 했던 내가 병신이다
내가 배운 내용중에 멀티스레드 시간 측정할만한 좋은 책이 없어서 그렇네
그럼 너는 멀티스레드 시간 어떻게 측정하는데? 난 clock() 사용하고 평균 CPU 사용량 구해서 계산해.
이런 단순 정보는 뇌피셜 써놓고 반박하는 사람한테 물어보지 말고 책을 찾아 아니면 검색을 하던가 니가 한 질문 그대로 구글에 복붙하면 무조건 나온다
정렬이야말로 멀티스레드 이득 각 잘 나오는 영역인데?
직접 돌려보니까 알 것 같네. 근데 분할 정복 계통 아니면 이득 거의 없을거 같은데? 아님 데이터량이 압도적으로 많으면서 정렬해야할 배열이 여러개된다면 의미가 있겠지만.
뮤텍스를 안 풀어서 생기는 데드락도 코드를 잘못 짜면 제법 잘 생김. RAII 패턴을 안 쓸 때 분기문에서 실수로 안 풀고 return/break를 한다거나 예외가 발생해서 흐름이 넘어간다거나...
멀티스레드 효율이 아주 잘나오는 곳은 io 많은 부분이고 엄청 많이 활용되죠. 걍 아무회사 들어가서 서버 백앤드하면 멀티스레드 혹은 멀티프로세스(이게더많을듯) 로 io 분산시키는데 그게 제일 효율적이기 때문이죠. 제일 많이 쓰이는 부분이 예로 안나오고 자꾸 정렬같이 계산집약적인 부분이 나와 답답했음. ,
네 그렇지요... 혹시 윗분이랑 아이피 같으신데 동일 인물은 아니시져? 반말 죄송합니다.
같은사람 아님요. 내가볼딴 님 열심히하는게 아주 멋지고 좋아보임. 호이팅 ,
io에서 필요한건 주로 동시성이고 병렬성하고는 초점이 다름. 계산집약적인것만 나와서 답답하다는 말은 좀 엉뚱한 소리임.
39.7 틀린말 없음
파이썬유저검거
Wait free 알고리즘 쓰면 됨 ㅅㄱ