Cliff Bleszinski’s Game Developer Flashcards

원문 Cliff Bleszinski’s Game Developer Flashcards

WordPress 기본 에디터가 엄청 불편하네요. 잘모르겠는 부분이나 제 생각은 이탤릭채+언더라인으로 표시하겠습니다. 직역한 것도 있고 그렇지 않은 것도 있습니다. 개인적으로 읽기 위해서 작성한 것이므로, 저작권에 문제가 된다면 언제든지 삭제될 수 있습니다.

Cliff Bleszinski 가 쓴 게임개발자의 플래시카드

By Cliff Blenszinski

Epic Games의 디자인 디렉터 Cliff Blenszinski 가 개발자 공통의 행동에 대해서 분석했습니다.

이번 여름에 들어서, 저는 20년간 게임을 전문적으로 만들어왔어요.  저는 캐릭터 마스코트 플래폼(캐릭터 IP를 이용한 게임인거 같음), FPS, 싱글 캠페인, 멀티플레이 경험, 그외 많은 게임들을 디자인해왔습니다. 수많은 대단한 프로그래머, 아티스트, 애니매이터, 작가, 프로듀서들과 함께 일했습니다. 그 시간동안, 저는 우리가 창의적인 전문가로서 소통하는 방법에 어떠한 패턴이 있다는 것을 알았어요.

개발자들은 엄청나게 똑똑하지만, 반면에 그들은 동료들과 비교해서 얼마나 똑똑한가에 대해서논할 때에는 조금 불안정해진다는 것을 배웠습니다. 개발자 게시판에서 수억달러의 프렌차이즈나 인디 개발자, 그리고 그 사이에 있는 사람들끼리 확대해석하고 트집잡으며 헐뜯는 것을 봐왔습니다. 우리는 항상 우리의 아이디어가 다른 누구도 생각하지 못했던 것임을 혹은 그런 아이디어가 이미 실험되었고, 성공했거나 실패했고, 이젠 영향력이 없는 아이디어임을 증명하기를 원해요.

짧게 이야기하면, 이 글은 게임 개발자들 사이에서 논의, 논쟁, 토론에서 승리하기 위해 자주 사용되는 의사소통 기술에 대해서 정의하고 있습니다.

이 관찰들과 이름(아래에 나오는 예들에 대한 타이틀인듯)은 누구를 적대시하기 위한 것이 아닙니다. 사실 여기서 언급된 몇몇 접근들은 유효한 추론에 의해서 사용되었습니다. 예를 들면, 패턴 매칭은 심한 경우 탄탄하다고 생각했던 아이디어를 죽일 수 있는 파생 제품을 만드는 것을 막기위한 좋은 방법이죠. 게임 개발 소통 플래시카드들입니다.

“Pattern Match Dismissal” – 패턴 매칭을 통한 기각

자기가 알고 있는 비슷한 아이디어(게임 혹은 대중문화의)들 중 나쁘거나 실패한 예들을 들어 반대하는 것입니다.

예를 들면, 영화 아바타에 대해서, “숲에 사는 시퍼런 사람들이 사악한 군대와 기계에 대항하는 영화를 만들고 싶다고? 스머프 외계인이야?” 기어즈 오브 워 프렌차이즈도 삐딱한 시선에서 보면 80년대 싸구려 호러영화 C.H.U.D처럼 보일 수 있어요.

“Edge Blocking” – 극심한 경우로 막기

잠재적으로 어마어마한 아이디어를 막기위해 극심한 경우를 예로 드는 것을 말합니다. 이 방법을 사용하는 사람들은 스카이림이 방대한 세계를 가지고 있다고 깔 수 있어요. 세계가 넓기 때문에 이동하는 것이 지루하고 고될수 있다는 걱정을 하면서요. “Fast travel” (포탈이나 추가적인 이동수단을 통해서 빠르게 움직이는 것을 말하는 듯   “Fast Travel은 플레이어가 한번이라도 가봤던 마을이나 스팟을 지도상에서 클릭해 바로 이동하는 기능이에요. 일반적인 포탈과 다른 점은 금전적인 비용은 전혀 없고 대신 게임내에서 목적지까지의 시간이 경과된다는 점이네요” 라네요. Brian Jo님 감사합니다.) 과 같은 개념을 도입하면 이러한 문제를 쉽고 명료하게 풀수 있는데 말이죠.

Edge Blocking 의 변종들:

The Networker.” 극심한 경우나 코옵 상황에서는 그 아이디어가 동작하지 않을 것임을 예로 드는 것.”맥스패인의 슬로우 모션은 코옵에서 어떻게 돌아가? 불가능하지!” (항상 멀티플레이를 예로 들기 때문에 Networker 라는 이름이 붙은게 아닐까 생각)

“Perfectionist.” Edge Blocking 과 마찬가지로 개발자가 좋은 아이디어가 완벽해보이지 않는 하나의 예를 찾아내는 것. 예를 들면 아수라장 같은 상황에서는 캐릭터들끼리 겹쳐보이게 될거야.

“Ne’er Do Well” – 절대 잘될리가 없다

혹은 “이전에 이런 기능을 본적이 없다 혹은 이런 기능이 잘된적을 본적이 없어요. 그러니까 이거 하면 안되” 따로 설명이 필요없는 항목이기도 한데, 사실은 이게 왜 이 아이디어를 실행해야 되는지에 대한 이유도 될 수 있습니다. 이러한 논리를 따라가다 보면 다른 사람이 성공한 것을 모방할 수 밖에 없어요.

한가지 언급하자면, Locust 주민들(기어즈 오브 워에 나오는 외계인들인듯)은 많은 이유에서 잘동작했지만, 우리는 아직도 그것에 대한 의구심을 자기도 있습니다. 그것은 마침내 천상/우주에서 온 외계인이라는 특징을 가진 보통의 다른 프렌차이즈들과 기어즈 오브 워가 차별화되는데 도움을 주었습니다.

“Devil’s Advocate” – 일부러 반대하기

대부분의 개발자가 그들이 어떤 아이디어를 지지하는 경우에도 변명의 한 형태로써 이 테크닉을 사용하는 경향이 있습니다.

“It’s just X+Y” – 그냥 두개 더한거구만

개발자가 다른 성공적인 제품을 묵살하기 위해서 오기를 부리는 것입니다. 왜냐하면 그 공식은 알아보기 쉽거든요. 공식이 쉽고 분명하다는 것은 왜 제품이 성공적인가라는 말도 됩니다.

예를 들면, Words with Friends: “이건 그냥 비동시 스크래블이군.” 네 맞아요 그래서 그게 멋진거죠.

“Future Release” – 다음에 넣자

개발자가 한 아이디어(좋거나 나쁜)를 듣습니다. 그리곤 말하는 거죠. 다음 버전이나 차기작에는 잘 맞을거 같아. “나는 그 아이디어가 좋은거 같지는 않은데, 니 기분 좋으라고 이건 다음에 하기로 하자고 이야기 하고 안할꺼야” 고 관례적으로 말하는 것입니다.

“Toppling,” a.k.a. “Tower of Babel” – 넘어간다아아

이 테크닉은 개발자가 어떤 간단한 기능에 너무 많은 부가기능을 붙여서 궁극적으로 이러한 추가때문에 그 기능이 위대롭게 되는 것을 말합니다. 그 기능이 무게를 이기지 못하고 쓰러지는 것이죠.

“Think of the Children!” – 애들을 생각해!

다르게는 연속적인 의존으로 알려져 있습니다. 애니매이션, UI, 아트와 같은 다른 부서에 더 많은 일을 해야된다는 이유로 그 아이디어를 죽이는 것입니다. 주로 흥미롭고 할만한 가치가 있는 기능들은 다른 부서들의 업무를 합해야하는 이러한 파급효과를 가지고 있어요.

“Analysis Paralysis” – 분석 불능

너무 생각만 과도하게 해서 실제로 아무것도 수행하지 못하는 것.

“Why Even Try?” – 왜하노(경상도식)

다른 말로 “우리가 어떻게 경쟁할 수 있겠어?” 경쟁자가 너무 많다고 위협을 느껴서 개발자가 성심성의를 다하기 전에 포기하기 위한 방법.

“They Have N Developers!” – 저 회사는 개발자가 그렇게 많다네

이 문구는 개발자들이 경쟁팀과 그들의 수가 얼마나 많은지 언급하기 위해 자주 사용되는 말입니다. 그리고 “Why Even Try?” 라고 말하기 위해서 자주 사용되요. 위대한 길은 항상 최고의 사람들에게 업무를 할당하고 최고의 툴을 제공해 스마트하게 일하는 것입니다. (크기가 중요한게 아니라는 말같네요)

“Traditionalist” – 전통주의자

“그렇지만 이게 우리가 항상 하던 방식인데!” 엔터테인먼트, 특히 기술 산업에서 혁신과 재고는 살아남기 위해서 필요합니다. 자기만족하거나, 어떠한 일을 같은 방법으로 계속 계속 하는 것은 실패로 가는 확실한 방법이에요.

규칙적인 업무의 20년 베테랑이 되는 것은 장점이 될 수도 있지만, 기술 산업에서는 아니에요. 그것이 때로는 당신을 제한할 것입니다. 개발자가 나이가 들수록 열린 마음을 유지하는 것과 항상 배우는 것이 중요합니다.

“But We’re (Insert Studio Name)” – 우리는 (누구누구)인데

이것은 현재의 성공에 안주하기 위해 준비된 사무실의 전쟁 함성입니다. 우리는 특정 레벨의 성공에 도달했고 이미 우리는 한가닥 하는 사람이라고 생각하는 것입니다. 사무실 사람들이 이 말을 사용하기 시작하면, 누군가는 스튜디오의 붕괴를 손꼽아 기다릴 것입니다. 왜냐하면 더 젊고, 더 배고픈 바깥에 있는 사람들이 우리가 가진 것을 원할테고 이루려 노력할것이고 이룰것이니까요.

“We Tried That Before” – 이거 전에 해봤어

이전의 실패를 상기시켜서 새로운 (성공 가능성이 있는) 아이디어를 죽이는 것.

“Too Cool” – 지나치게 끝내줘

너의 아이디어는 죽여줘! 사실, 이건 너무 끝내주고 혁신적이야. 그래서 우린 이걸 하면 안돼. 왜냐하면 일이 많아질거 같거든.

“Jargonating” – 전문용어로 압박주기

개발자가 다른 부서와의 논쟁에서 이기기 위해서 그의 부서에서만 통할 전문적인 용어를 사용하는 것입니다.

“The Tribal Leader” – 내가 갑이다

개발자가 자기의 분야(아트, 코드, 디자인 등)이 스튜디오의 다른 사람보다 뛰어난다고 믿기 때문에 다른 애들은 X까라고 하는 것.

“Noscope” – 시야에 없어

“이 아이디어는 멋져 그런데 이건 우리 프로젝트의 영역이 아니야.” 때로는, 불행하게도, 최고의 기능은 주변에 레이더 아래에 숨어있거나 원래 예정에는 없던 것입니다.

“Playtest Grandstanding” – 테스트때 달려서 고칠게

개발자가 새로운 기능이나 무기가 충분히 준비되지 않았는데 플레이 테스트 동안 고쳐서 밸런스를 잡겠다고 외치는 것입니다. 가끔 유저들은 스나이퍼건을 들고 아무것도 할 수 없고 다른 유저들에 의해 죽어나가도 그것은 괜찮아요.(스나이퍼건이 제대로 만들어지지도 않았다는 뜻인듯)

“The Repitcher” – 아이디어 도용자

누군가 내 아이디어를 듣습니다. 처음에는 내 그걸 듣는채도 안하더니, 어딜가서는 똑같은 아이디어를 그들의 언어로 어디서 들었는지는 잊어버리고 자기것인냥 말합니다. 이것은 좋은 아이디어가 어딘가로부터 와서 잘 구현되기만 한다면, 궁극적으로 크게 중요하지는 않아요.

“Filibuster,” or “TL;DR Guy” – 의사결정을 방해하는 사람

(TL;DR은 too long, didn’t read)

이 사람은 디자인 제안이나 논의에 대해서 3페이지 분량을 메일을 보냅니다.

맨날 보냅니다.

잊어버리지도 않구요.

결국에는 당신은 이 사람의 메일을 필터링할것입니다.

“The E-Douche”

항상 이 사람이 보낸 메일을 읽으면 병신같습니다. 심지어 진정 그런 의미로 보낸게 아니라도, 솔직히 메일쓰는 능력이 없어서요.

“Godzilla” – 고질라

이 사람은 현재 진행되고 있는 사항들을 모두 갈아엎고 주관적으로 더 괜찮아 혹은 더 나쁜 새로운 아이디어로 바꾸는 사람입니다. 그래서 본질적으로는 모두 처음부터 시작하게 합니다.

“The Doubter” – 불확신자

어떤 아이디어를 아무런 분명한 이유없이 거부하는 사람입니다: “난 그건 잘 몰라..” 가끔 … 를 상대할 때는 효과적입니다. (… 가 누구인지 잘 모르겠네요. 하기싫은 일을 시키는 사람인가?)

“Prophet” – 예언자

어떤 아이디어에 흥미를 가지고 달려드는 하지만 그것의 디자인이나 파급효과에 대해서는 전반적으로 생각해본적이 없는 디자이너입니다. 간단하게 모든 사람들이 그 아이디어에 대해 잘될거라는 믿음을 가지고 있다고 디자이너 혼자 예상하는 것입니다. 그 경우에 대해서 제대로 생각하지도 않았는데 말이죠. 이것은 주로 젊고, 경험이 덜한 디자이너에게서 나타나는 흔한 행동입니다.

“Captain Ahab” – 경주마 같은 양반

(“모비 딕의 등장인물입니다. 모비 딕을 잡기 위해 끝없이 집착하는 것에 비유한 듯” – 라네요. 하얀까마귀님 감사합니다.)
디자이너가 어떤 아이디어가 잘 진행되고 있지 않음을 인정하지 않고 끝없이 계속하는 것입니다. 소중한 코드와 아트 리소스들을 사용하다보면 언젠가는 재미있어 질거라 믿고요.

“Data Bombing” – 데이터 폭발

이런 논의는 이런 식으로 흘러가기 마련: “이 막연하게 연관되어 있고 완벽하게 공정하신 데이터님께서 너의 아이디어가 절대로 성공할수 없음을 보여주고 있지. 많은 사람들은 너를 불쾌하게 여길 걸. 이건 명령이야. 우린 이걸할 여유가 없어”

“Psychic Expectations” – 초자연적인 기대

코더들에 의해서 사용되어지는 테크닉인데, 코더가 특정 기능을 자기가 듣길 원하는 특정한 방식으로 듣기 전까지는 이해하기를 거부하는 것입니다.

“Ignorannihilation”
(Ignorance + annilihation 라고 합니다 – 하얀까마귀님 감사합니다.)

개발자가 의도적으로(혹은 고의가 아니게) 너무 빤한 것을 이해하지 못하고, 잠재적으로 좋은 기능에 굶주려 있는 상태. 그 기능을 없앨때 까지 지속됨.

“Not My Idea, Not Going To Do It” – 내 아이디어 아닌데요. 안할겁니다.

디자이너가 다른 사람에게 아이디어를 제공받고 그 아이디어에 대한 판정을 요구하고 있는 상태. 그동안은 자기스스로 만든 아이디어말고는 조용히 모든걸 무시한다.

“The Gardener” – 정원사

정원사는 어떤 아이디어의 씨앗을 일찍 뿌려놓고 미팅과 평상시 대화에서 자꾸자꾸 그 이야기를 꺼냅니다. 결국에는 그 아이디어가 뿌리를 내리고 자라서 실재로 게임내 기능이 되고, 아무도 그 아이디어가 처음에 어디에서 왔는지 기억못하게 됩니다. 이것은 실제로 매우 유용한 테크닉이에요.

“Obvious Bug Guy” – 명백한 버그가이!

개발자가 개발중인 기능을 보여주기 시작합니다. 그 기능이 어디로 향하고 있는지, 무엇을 할 수 있는지는 생각하는것 대신에, 분명히 지금은 잘동작하지 않지만 궁극적으로는 완벽하게 고쳐질 버그(예: Z-fighting)를 보여주고 싶은 욕구를 느낍니다.

“Multiboss” – 누구 장단에 맞추라고

한 사람이 명백하게 정해진 상사나 명령 체계가 없는 상태입니다. 그래서 주로 어려 사람에게 어떤 일을 해야하는지, 어떤 일에 촛점을 맞춰야 하는지 전달받습니다. 그 사람의 디자인 디렉터나, 제작책임자, 사장님 각각은 그가 무엇을 해야할지에 대한 다양한 의견을 가지고 있고, 그를 혼란에 빠트릴 것입니다.

“The Promiser” – 대변인

이 용어는 미디어에 어떤 기능을 약속하는 대변인을 의미합니다. 한번 대중에게 공개되버린 그 기능을 구현하느라 그는 팀을 궁지에 몰아세웁니다.

“The Bandwagoner” – 1등만 따라가는 더러운 놈

이 용어는 최근 해본 유명한 게임들에 들어간 기능이라면 무엇이든 추가하길 원하는 창의적인 사람을 의미합니다. 그것이 게임을 더 좋게 하는 방법이라고 하면서요. 혁신하기 위한 다른 방법은 찾지 않고요.

끝으로, 여기까지가 제가 이제까지 만난 성격과 테크닉들이었습니다. 블라블라..

다시 한번 원문 Cliff Bleszinski’s Game Developer Flashcards

기본 워드프레스에서는 각주가 지원이 안되는 군요 ㅠ 중간중간에 문맥이 끊기는 부분이 있네요.

XCode 디버깅 중 배열내용보기

gdb 상에서는

gdb) p *array@length 로 쉽게 할 수 있는데

xcode 에 있는 lldb 는 이게 지원이 안된다.

(lldb) p array
(int *) $8 = 0x0000000105600a50

(lldb) memory read -fd -c10 0x0000000105600a50
0x105600a50: 1
0x105600a54: 2
0x105600a58: 3
0x105600a5c: 4
0x105600a60: 5
0x105600a64: 6
0x105600a68: 7
0x105600a6c: 8
0x105600a70: 9
0x105600a74: 0

즉 p (var) 로 시작 주소를 따온 다음에

memory read -f(type) -c(length) (start address)

로 보면 됨.

게시자: breadceo Study에 게시됨

Post Hoc Ergo Propter Hoc

After therefore because of it

이 후, 따라서 이것 때문에

A이후에 B가 발생했으니 A의 이유는 B이다

항상 그런것만은 아니다.

게시자: breadceo 근황에 게시됨

if not

책읽는데 다음과 같은 문장이 나왔다

Data-checking and verification is one of the most importantㅡif not the most importantㅡpart of graph design.

암만봐도 Data-checking과 verification 이 graph design 할때 가장 중요한 부분중 하나라는건 알겠는데

if not the most important 이게 도통 무슨 말인지 모르겠다

네이버 영영사전님께서 도움을 주셨다

3. used to suggest that something may be even largermore importantetcthan was first stated

그리고 깨알같은 예문

Alex Ferguson was one of theif not THE first

알렉스 퍼거슨은 첫번째가 아니라면, 첫번째들중 하나였을 것이다.

그래 이거야!!!

Data-checking과 verification 이 graph design의 가장 중요한 부분이 아니라면 가장 중요한 부분 중 하나이다.

 

게시자: breadceo English에 게시됨

Hobart 일기 1탄

이제 한국을 떠난지 1주일 정도 되었다.

어찌됐건 호바트에 잘도착했다. (이젠 공항이 무섭쥐 않아~)

주소는 20 Fizroy Place, Sandy Bay 7005, Hobart, Taz, Australia.

신기하게 지도에 영국지명 이름이 많이 보인다.

지금 살고 있는 집

이런 모습인데 처음에 봤을때는 진짜 놀랐다. 뭐 이렇게 큰거야 ㅋㅋㅋ

실내도 매우 넓고 좋다. 근데 단점은 난방따위 없다. 욜라 춥다. ㅋㅋ 특히 아침에 자고 일어났을 때의 그 오한이란;;

이것은 나의 방.

(침대곁에 있는 아이유 사진은 모른체 하자.)

처음에 왔을때는 이 잉여를 감당하지 못하고 (그렇다고 디아블로가 되는것도 아니고) 친구도 없고, 춥고, 배고프고 해서 집에 가고 싶었는데 하하..

컴투스에서 같이 일하던 Jeff, Joe, Peter, Ben 친구들의 마음이 이제야 이해가 된다. 돌아가면 더 잘해줘야지..

이제는 어느 정도 적응을 해서 그런지 부엌에 가서 잘 꺼내먹고 해먹고 한다. 하하..

막상 이렇게 보니 집이 완전 마르지 않는 샘같다. 정말 먹을게 많다. 까딱하면 몸무게 3자리 갈지도 –;

영어 공부는 지겹다, 이제. (이래도 못하니까 문제지만..)

재미없는거는 아닌데 아 역시 General English, 특히 단어부분은 너무 너무 약해서, 복습도 하고 해야되는데 귀차니즘 발동 ㅋㅋ

주말에 이 잉여를 잘 폭발시킬 수 있으면 완벽하게 적응할듯

게시자: breadceo 근황에 게시됨

3장. 스레드

스레드의 세부사항

윈도우 스레드란?

스레드는 프로그램이 작업을 처리하는 실행 컨텍스트(execution context)

명령어 포인터(instruction pointer) : 현재 실행 중인 명령어의 위치를 가리킴

‘실행’ 과정
fetch : 스레드 코드에서 프로세서가 다음에 실행할 명령어를 가져옴
decode : 해당 명령어를 해독
실행
IP 를 조정 : 일반 명령어인 경우에는 단순히 증가, 분기나 함수 호출인 경우에는 적절하게 조정

레지스터는 물리적으로는 프로세서내에 있지만 레지스터의 상태 역시 스레드가 올바르게 동작하기 위해 필요한 정보 (스레드 실행이 멈췄다가 다시 실행될 때 레지스터의 상태를 복구해야만 함)

컨텍스트 스위치(context switch) : 레지스터의 상태를 메모리에 저장하거나 복구하는 과정

CLR 스레드란?

윈도우 스레드가 원시 코드를 실행하는 것이라면 CLR 스레드는 관리되는 코드(managed code)를 실행

OS는 관리되는 코드에 대해 아무것도 모르기 때문에, 윈도우 스레드에 CLR 에 관련된 정보를 덧붙여서 만듬

CLR 호스트(SQL 서버, ASP 닷넷)에서 메모리 관리, 처리되지 않은 예외를 다루는 방식 같은 정책을 변경할 수 있게 여러 인터페이스를 제공하고 이것을 위해서 윈도우 스레드와 CLR 스레드가 분리되어 있음

명시적 스레딩과 대안

스레드풀 쓰세여

스레드의 탄생과 죽음

2장. 동기화와 시간

Introduction

상태(state)는 동시성 프로그램을 작성할 때 중요한 개념

동일한 공유 메모리에 읽기와 쓰기 작업이 동시에 진행되므로, 적절히 처리하지 않으면 문제 발생(data race, race condition)

동시성 시스템에서 상태를 관리할 때 주로 사용하는 기법

  1. 독립성(isolation) : 동시성 시스템을 구성하는 부분들이 상태를 독립적으로 관리
  2. 불변성(immutability) : 읽기 전용, 절대 변경되지 않는 성질
  3. 동기화(synchronization) : 하나의 공유 메모리에 여러 스레드가 동시에 접근할 때 안전하게 처리하기 위한 방법대기(waiting)와 알림(notification) 기법이 주로 사용데이터 동기화(data synchronization) : 데이터 처리에 사용되는 동기화제어 동기화(control synchronization) : 상태 등을 적절히 처리하기 위한 동기화

프로그램 상태 관리

공유 상태(shared state)란 무엇일까?

‘두 개 이상의 스레드에서 동시에 접근 가능한 모든 상태’

공유 상태/비공개 상태 식별

어떻게 구분할 수 있나?

동시성 시스템을 어떻게 구현했느냐에 따라 달라진다 (물론..)

  • 전역(global) 변수나 정적 필드가  참조하는 모든 상태는 공유 상태
  • 스레드를 생성할 때 전달되는 모든 상태도 공유 상태
  • 위에서 말한  조건을 만족하는 상태에서 접근이 가능한 다른 상태들 역시 공유된 상태

공유는 전이적(transitive)인 특징이 있다

데이터의 공유상태가 변경 되는 작업을 데이터 공개(data publication)와 데이터 비공개(data privatization)라고 한다

공유 상태가 되면 객체에 대한 소유권이 어떻게 이전되었는지 파악하기가 힘들다

예) class C

동시성 환경에서는 C 클래스의 동일한 인스턴스에서 여러 스레드가 동시에 f 메소드를 호출 할 수 있음

<공유 상태가 아닌 것>

  • The only memory location not shared with other threads is the x variable, so the x++ statement doesn’t modify shared state.
  • py++ 이란 구문이 있었다면, py 는 값에 의한 전달(pass by value) 이므로 공유된 상태로 볼 수 없음

<공유 상태인 것>

  • s_f 변수의 경우 정적(static) 변수의 특성상 여러 스레드에서 해당 메모리에 대한 동시 접근이 가능하므로 공유된 상태로 볼 수 있다
  • 정적 클래스 변수 대신 정적 지역(local) 변수를 썼더라도 동일

<정보가 부족한 것>

  • m_f++ 구문은 공개되지 않은 메모리에 대한 연산이므로 공유 상태가 아닌 것처럼 보이지만, 이것은 호출자가 C의 인스턴스를 어떻게 사용했느냐에 따라 다름m_f++ 은 (this->m_f)++ 와 동일한데, this 포인터는 실행 중인 스레드의 스택에 할당되거나 힙에 동적으로 할당된 객체를 가리킬 수 있으므로 스택에 할당된 경우는 공유 상태로 볼 수 없고, 힙에 할당된 경우는 공유된 상태로 볼 수 있다
예) class D

<공유 상태가 아닌 것>

  • c1.f(&x) 에서 인스턴스 c1 은 스택에 할당되었으므로 외부에 공개되지 않음
  • c2.f(x) 에서 c2 는 힙에 할당되긴 했지만 다른 스레드와 공유하지 않으므로 공개되지 않음 (f 호출 전에 C 생성자에서 자기 자신에 대한 참조를 외부에 공개하면 멸ㅋ망ㅋ)

<공유 상태인 것>

  • s_c 는 정적 변수로 외부에 공개되어 있음

<정보가 부족한 것>

  • m_c 는 또 ‘상황에 따라’ 다름, D 의 인스턴스가 어디에 할당되었느냐에 따라서 m_c 의 공유 여부가 결정됨

탈출 분석(escape analysis) 이란 기법으로 비공개된 메모리가 공유 메모리 영역으로 ‘탈출’하는 경우를 판단할 수 있음

but, 프로그래밍 언어에 제약을 두지 않고는 적용이 어려움,

false negatives (탈출하지 않았다고 했는데 실제로는 탈출한 경우) 우려

소유권 형식(ownership type) 같은 아이디어도 연구중

이래서 동시성 프로그래밍이 어렵다!

상태 기계와 시간

순차적으로 실행되리라 가정하고 작성된 코드라도 여러 쓰레드가 동시에 실행되면 시간이 겹치는 구간이 생김

데이터 경쟁의 간단한 예

이 간단한 구문이 컴파일되면

이렇게 여러개의 기계어로 변환된다

a 가 가리키는 가상 메모리 주소에서 네 개의 바이트를 EAX 레지스터에 복사

EAX 레지스터의 내용을 1 증가

증가된 값을 a 가 가리키는 가상 메모리 주소에 복사

여러개의 하드웨어 명령어를 요구하는 소프트웨어 연산은 어떤 경우이든 비원자적(nonatomic)이다

최근 프로세서는 기본 워드 크기의 메모리(32bit 프로세서면 32bit, 64bit 프로세서면 64bit)에 대한 읽기나 쓰기연산은 atomic 하다고 보장된다.

그렇다면 뭐가 문제?

<운이 좋은 경우>

<전형적인 데이터 경쟁 상태>

결과는 2가 나와야 하지만 1이 나옴

위험 요소로 분류
  • 읽기 / 쓰기 위험성: 스레드 t1 이 특정 메모리에서 값을 읽고, t2 가 해당 내용을 다른 값으로 변경한 후 t1 이 기존에 읽은(이미 t2의 변경 때문에 유효하지 않게 된) 값을 사용하는 경우.오래된 데이터 읽기(stale read)라고 함
  • 쓰기 / 쓰기 위험성 : t1 과 t2 가 같은 메모리에 대해 서로 다른 값을 동시에 쓰는 경우 이전 값을 덮어버림
  • 쓰기 / 읽기 위험성: t1 이 특정 메모리에 값을 쓴 후 아직 읽어서는 안되는 상황에서 t2 가 메모리 값을 읽는 경우, t1 수행 중 실패로 인해서 값을 돌리는 경우 t2 는 되돌리기 전의 잘못된 값을 사용하게 됨반복할 수 없는 읽기(unrepeatable read)라고 함
  • 읽기 / 읽기 위험성:  일반적으로 공유된 데이터를 동시에 읽는 경우에는 문제가 없다읽기 간에 충돌이 있는 경우 성능 향상을 위해 reader/writer 락을 사용할 수 있다

원자성, 직렬성, 선형화

원자성(atomicity) : 하나의 연산이나 여러개의 연산이 중간에 나눠지지 않고 한번에 처리된 것처럼 보이는 속성
  • 값을 갱신하는 중간에 실패하면 안됨
  • 실패 가능성이 있는 경우에는 rollback 할 수 있어야 함
  • 하위 레벨(메모리 읽기, 쓰기)의 개념으로 이해하는 것보다 상위 레벨(‘디스크에 데이터 쓰기’)으로 이해하는 것이 유용
직렬성(serializability) : 두 개 이상의 연산이 순서대로 처리
  • 원자적 연산을 기본 단위로 프로그램을 작성하면 직렬성도 갖게 됨
  • 직렬성만 가지고는 프로그램의 정확성을 보장할 수 없음
선형화(linearizability) : 직렬성과 관련된 속성, 원자적 연산의 정확성을 설명하는데 사용
  • 선형화 지점(linearization point) : 원자적 연산의 실행 결과가 다른 스레드들에게 보이게 되는 시점
  • 임계 영역(critical section) 내에서 연산이 처리된 후 해당 영역을 벗어나는 지점과 일치

원자적 연산 간에는 특정 연산이 다른 연산보다 먼저 처리되도 프로그램이 정상적으로 동작 가능하게 순서를 자유롭게 바꿀 수 있어야 한다. 물론 순서가 바뀌어도 프로그램의 처리 내용이 서로 동일해야 한다는 것을 의미하는 것은 아니며, 바뀐 순서에 의해 프로그램의 정확성이 근본적으로 바뀌어서는 안된다는 말이다.

격리성

  • 프로세스 격리(process isolation) : 윈도우에서 프로세서는 서로 독립적인 메모리 주소 공간에서 동작하므로 다른 프로세스의 메모리를 읽거나 쓰는 작업이 근본적으로 허용되지 않음
    프로세스 간에 공유되는  자원도 있으므로 완벽히 분리되었다고 할 수는 없음
  • 프로세스 내부에서의 격리(intraprocess isolation) : managed code 를 사용한다면 CLR 의 어플리케이션 도메인(appdomain)을 사용해 객체 간의 읽기, 쓰기 작업을 도메인에 한정할 수 있음
  • 규정(convention)을 통한 격리 : 코드에서 동적으로 힙이나 스택에 메모리를 생성하고 그것을 이미 공유된 곳에서 해당 메모리를 참조하지 않게 규정에 따라 관리하는 것

상태를 특정 시점에서 특정 ‘에이전트’가 ‘소유’한 개념으로 볼 때 쉽게 이해되기도 한다

데이터 소유권

소유권 : 다른 컴포넌트나 에이전트 들이 해당 데이터에 동시에 접근할 수도 있음을 에이전트가 알고 있고, 이 경우에 에이전트가 연산을 수행할 때 읽기 및 쓰기 안정성에 어떤 영향이 있는지를 확실히 알아야 된다는 의미

소유권은 이전할 수 있지만, 세심한 처리가 요구됨

  • 공개화(publication) : 에이전트가 객체를 생성하고 초기화 한 후, 해당 객체를 전역적으로 공유된 리스트에 추가
    소유권은 에이전트로부터 네임스페스로 옮겨감
  • 비공개화(privatization) : 공유된 리스트에서 해당 객체를 제거
    소유권은 네임스페이스에서 에이전트로 이전

데이터에 대한 소유권 처리가 까다로운 이유는 데이터가 공유된 상태와 격리된 상태간에 잦은 전환이 일어나기 때문

또한, 데이터가 격리되는 시점을 정확하게 파악하기도 어려움

불변성 : 공유 상태가 전혀 변하지 않는 속성(읽기 전용과 마찬가지)이 보장되면 동시에 여러 스레드에서 접근해도 문제가 없음

얕은 불변성(shallow immutable) : 객체가 살아있는 동안 해당 객체의 모든 필드가 변하지 않을 때

깊은 불변성(deeply immutable) : 객체의 필드가 참조하는 다른 객체들 역시 상태가 변하지 않는 경우

데이터가 비공개 상태와 공개 상태간의 전환이 자주 일어나는 경우 불변성을 활용하고 싶을 때에는 공유된 상태에서는 불변성을 유지하고, 비공개된 상태에서는 변경이 가능한 식으로 조건적으로 불변성을 지원하는 타입을 이용하는 것도 한 방법

단일 할당(single assignment)

CLR : initonly 한정자, C#에서는 readonly 키워드

  • 필드를 readonly 키워드로 표시하면 해당 변수에 대한 쓰기 작업은 객체의 생성자나 필드 이니셜라이저에서만 허용

C++ : const 한정자, type cast 를 통해 ‘상수성’을 없앨 수도 있음

  • 클래스의 필드를 const 로 지정하면 필드 값은 생성자의 이니셜라이져 리스트에서 초기화, 그 뒤로는 변경되지 않음, 포인터의 형 변환을 이용해 변경할 수는 있긴 함
    static const  필드의 값는 변경 불가! CLR 에서 initonly 와 비슷
    static const 필드는 컴파일러 상수, 정적으로 알려진 주소, 그러한 것들의 배열이 인라인으로 할당된 것으로 제한된다
  • 클래스의 멤버 함수에도 const 를 지정할 수 있으며, 이 경우 함수는 클래스의 멤버 필드를 변경할 수 없고 const 가 아닌 다른 멤버 함수를 호출할 수도 없다
  • const 가리키는 포인터

동기화: 종류와 기법

  1. 데이터 동기화(data synchronization) : 메모리 등과 같이 공유된 자원이 여러 스레드에서 동시에 사용되는 경우
    뮤텍스(mutual exclusion)를 사용해 해결
  2. 제어 동기화(control synchronization) : 스레드는 프로그램의 실행 흐름이나 상태 측면에서 볼 때 서로 영향을 주고 받음
    다른 스레드나 스레드 집합이 특정 시점에 도달할 때 까지 기다려야 하는 경우나 특정 스레드가 다른 스레드를 관리해야할 때

정확성(정확한 코드를 구현할 수 있는지), 성능, 활동성(liveness), 확장성(프로세서 추가할 경우 성능이 향상되는가) 정도의 기준으로 동기화 구현을 정할 수 있다.

데이터 동기화

임계 영역(critical region) : 다른 스레드가 동시에 실행할 수 없고 순서대로 처리하게 설정된 일련의 코드 집합

S 코드영역을 실행할 수 있는 스레드는 한 번에 하나 뿐임 – 원자적으로 처리되는 것이나 다름없다
물론, S 에서 처리되는 공유 변수나 상태를 임계 영역 밖에서 처리하는 스레드가 없어야 하고, S 가 실행되면 중간에 실패하지 않는다는 가정이 필요

임계 영역을 일반화한 세마포어

세마포어 : 지정된 개수만큼의 스레드가 임계 영역에 동시적으로 들어갈 수 있게 처리하는 기법, 다익스트라(Dijkstra)가 만듬

접근 카운트가 0 이상일 때 스레드는 카운트를 1 감소시키고 임계 영역으로 진ㅋ입ㅋ

동기화 사용 패턴

상호 배재(mutual exclusion)가 보장되지 않는 예

동기화 자체도 이미 충분히 어려운데, 임계 영역을 여러 함수에 걸쳐 나눠 놓으면 더욱 구현이 어려워진다

고아(orphaned)락 : 락이 해제되는 것을 확실히 보장하지 않아 다른 스레드가 해당 영역에 들어가려고 할 때 들어갈 수 없는 것, 데드락의 원인

실제로는 처리 중간에 단순히 락을 해제하는 것만으로는 충분하지 않다
원자성을 보장하는 속성(순간적으로 처리된 것처럼 보여야 하고 처리 중간에 실패하지 않아야함)을 잘 생각해보면
문제가 발생하자 마자 락을 바로 해제하면 데이터가 손상될 수 있다

데이터 손상보다 차라리 데드락이 디버깅하기 쉬우므로 실패시 데이터를 되돌리지 힘들다면 그냥 두는게 낫다

큰 단위와 작은 단위 영역

큰 단위(coarse-grained) 임계 영역

모든 서브시스템이나 복합 데이터 구조의 모든 구성 요소를 보호하는 데 한개의 락만 사용

잘못된 공유(false sharing) 우려 : 프로그램이 정확하게 동작하는 데 아무런 상관이 없는데도 데이터에 대한 동시 접근을 막는 경우

작은 단위(fine-grained) 임계 영역

서로 관련없는 데이터는 동시에 여러 스레드에서 처리될 수 있게 연관된 데이터 집합별로 락을 사용

프로그램의 활동성과 확장성을 높힌다

데드락의 우려, 데이터 경쟁을 피하기가 훨씬 어려움

임계 영역 구현 방법

임계 영역의 요구사항

  1. 상호 배제 속성, 동시에 여러 스레드가 한 개의 임계 영역에 들어갈 수 없어야 함
  2. 임계 영역을 들어가고 나가는 과정에 문제가 생기면 안됨
    Given an infinite amount of time,  each thread that arrives at the region is guaranteed to eventually enter the region, provided that no thread stays in the region indefinitely
  3. 어느 정도의 공정성이 필요, 처리 순서가 정확하게 예측 가능할 필요는 없지만, 통계적으로 봐서 충분히 공정한 처리를 보장해야 함
  4. It is important that entering and leaving the critical region be very inexpensive

최근에는 하드웨어에서 원자성을 보장하는 비교와 교환CAS(compare and swap) 연산을 사용해서 뮤텍스를 구현

망한 케이스ㅋ

엄격한 교환 방식(strict alternation)

상호 배제는 가능!

공정성은 없음. 임계 영역에 도착한 순서를 전혀 고려 안함 (정해진 순서대로 처리되므로)
즉, 아직 임계 영역에 들어가 있지도 않은 스레드 때문에 다른 스레드가 진입하지 못하는 상황 발생 -> 활동성(liveness)에 악영향
잘못된 경쟁은 성능이나 확장성을 저해시킴
스레드가 다음 스레드에 소유권을 넘기기 위해서는 임계 영역을 나가야 하므로, 스레드들이 주기적으로 임계 영역을 들락날락하는 경우에만 제대로 작동!
동적으로 스레드 수가 바뀔 때는 사용 못함

데커와 다익스트라의 알고리즘(1965년)

flags 라는 완충재를 하나 두었음, 그리고 두개의 스레드가 동시에 진입하는 경우는 turn 변수를 사용

다이직스트라는 이 알고리즘을 조금 바꿔서 N개의 스레드에서도 동작하게


(j == i || flags[j] != F.Active) == false 일 경우에는 j < N 이 되어서 기다림
대우는 (j != i && flags[j] == F.Active) ==  true 이고 이게 더 알아보기가 쉽다

위의 두 알고리즘은 컴파일러에서 코드 최적화 하는 과정에서 실행 순서가 바뀔 가능성이 매우 높은 요즘에는 제대로 동작하지 않음

피터슨 알고리즘(1981)

임계 영역을 사용할 수 있거나 내 턴이 될 때까지 기다림

램포트의 빵집 알고리즘(1974)

빵집(요즘으로 말하면 은행과 비슷하겠다)이 돌아가는 모습과 비슷하기 때문에 빵집 알고리즘이라고 불림
티켓을 뽑고 내 티켓번호보다 낮은 번호(먼저 온) 스레드의 처리가 끝날 때 까지 기다림
같은 티켓 번호를 뽑을 수 있기 때문에, 스레드끼리 구분할 수 있는 인자(thread_id) 같은게 있어서 이게 낮은 스레드가 먼저 처리됨

임계 영역 진입 처리가 공정해지는 장점이 있다

하드웨어 비교 후 교환(CAS) 명령어 (현재)

하드웨어에서 원자적으로 처리되는 비교후 교환(compare and swap) 명령어들을 지원하기 시작

Win32 와 닷넷 계열에서는 interlocked 계열의 연산으로 정의

CAS 함수는 세개의 인수를 받는데

  1. 읽고 쓸 메모리 주소가 들어있는 포인터
  2. 해당 메모리에 저장하려는 값
  3. the value that must be in order for the operation to succeed

CAS 함수의 리턴은  3의 값이 1의 포인터가 가리키는 메모리의 값과 같고 그래서 2의 값이 메모리에 저장되는 경우 true 를 반환
반대로 3의 값이 1의 포인터가 가리키는 메모리의 값과 일치하지 않으면 false 를 반환

입계 영역에 진입하려는 스레드는 taken 값이 0 일 때에만 taken 변수에 1을 쓰려고  시도하고 이 처리는 원자적으로 수행됨
다른 스레드가 임계 영역을 나가면 taken 값은 0이 되고, 계속 시도하던 스레드는 taken 변수에 1을 저장하고 진입

순서 재배치와 메모리 모델의 어려움
  • 위에서 turn 변수를 사용하고 루프 안에서 turn 변수의 값을 다시 읽으면서 값이 해당 스레드의 인덱스 i 와 같아질 때까지 대기하는 방식을 사용했는데, 루프안에서 turn 변수에 값을 쓰지 않기 때문에 컴파일러 입장에서 최적화 해버릴 여지가 있음
    이렇게 되면 루프 바로 앞 부분에 임시 변수를 생성해서 turn 의 값을 미리 읽어놓는 사태가 벌어지는데 그러면 무한루프에 빠짐
  •  데커의 알고리즘이 제대로 동작하려면 스레드가 flags 배열에 쓰는 처리가 다른 스레드들의 flags 값을 읽는 작업보다 먼저 처리되어야 함
    최근 프로세서는 명령어 순서를 바꿔서 처리하는 것이 일반적이므로 데커의 알고리즘이 제대로 작동하지 않음
  • 임계 영역은 영역 안에서 처리된 데이터 쓰기 작업을 이후에 동일한 임계 영역 내에서 읽는 처리를 수행하는 스레드에게 전달하는 효과가 있다
    즉 스레드 t1 이 a++ 을 수행하고 난 뒤에
    스레드 t2 가 a++ 을 수행한다면 스레드 t2 는 스레드 t1 이 최종적으로 쓴 값을 읽게 됨
    이것도 컴파일러 최적화 과정에서 읽기나 쓰기 작업을 임계 영역 밖에서 처리하게 초드를 생성할 수도 있음
    프로세서 역시도 읽기와 쓰기 처리의 순서를 바꾸는 경우가 있음

요즘 프로세서, 컴파일러, 런타임 들은 어떤 최적화가 적법하고 적법하지 않은지를 메모리 모델을 이용해 지정한다

Win32 나 닷넷 프레임워크를 사용하면 이런 메모리 모델을 정확하게 이해하지 않아도 됨, 대부부분의 경우 이런 도구만으로도 충분히 처리 가능 함

조정과 제어 동기화

스레드간의 상태 의존성

앞에서도 이야기 했지만 프로그램을 거대한 상태 기계를 모아 작성한 것으로 볼 수 있다
스레드도 자체적인 상태 기계에 따라 동작한다고 볼 수 있다
여기서 스레드가 처리하는 작업이란 메모리 읽기나 쓰기 도중에 스레드 고유의 상태나 프로그램의 상태에 전환을 가져오는 처리를 의미
여기서 상태가 깨지지 않으려면 적절한 동기화 처리가 필요

내부 상태와 외부 상태를 구분할 필요도 있다
내부 상태 : 스레드 처리 시 내부 구현에만 필요한 상태
외부 상태 : 시스템 내의 다른 스레드에서도 접근할 수 있는 상태

스레드는 뭔가 작업을 처리하기 위해 시스템 내의 다른 스레드들과 상호 작용할 필요가 있고 결과적으로 스레드 간에 의존성이 생김

약간 추상화 된 예시
조건 P 가 참인 집합을 SP 라고 하자
P 가 참인 경우 스레드가 작업을 진행할 수 있다면 실제로는 SP 에 포함된 여러 가지 상태 중 한가지 상태로 전환되기를 기다리는 것과 같다
조건 P 의 값을 검사하는 것은 실제로는 ‘프로그램이 특정한 상태에 있는가?’ 라고 묻는것과 같다
만약 답이 ‘아니오’ 라면 할 수 있는 것은 3가지 정도인데

  1. 프로그램이 현재 상태에서 SP 에 포함된 상태로 전환되게 메모리 읽기나 쓰기를 수행
  2. 시스템 내의 다른 스레드가 1 의 처리를 할 떄 까지 기다림
  3. 일단 무시하고 다른일 하 기

2와 관련된 예가 임계 영역이다

이벤트가 발생할 때까지 대기하기

루프를 돌면서 반복적으로 대기(busy spin waiting)

S 는 P 에 의해 보호된다라고 함

루프를 도는 처리에는 CPU 사이클이 소모된다
대기 중인 스레드 또한 실행가능한(runnable) 상태로 유지되기 때문에, 진짜 필요한 작업을 처리하는 스레드가 실제 작업과는 상관없이 대기만 하는 스레드 뒤에서 프로세서를 할당받을 때 까지 기다려야 한다
결국 루프를 도는(busy wait) 스레드나 실제 작업을 처리하는 스레드 모두에게 성능 저하가 생긴다

운영체제 커널단의 진정한 대기 알고리즘

OS 수준에서 제공하는 커널 객체들

이벤트를 이용한 대기 매커니즘
특정 스레드를 대기 상태로 바꾸거나, 대기 중인 스레드를 꺠울 수 있다
대기 중인 스레드는 실행 가능한(runnable) 상태가 아니라 대기(wait) 상태에 있고, 스레드가 대기 상태일 때 컨텍스트 스위치가 발생해 프로세서에서 바로 제고되고, 윈도우의 스레드 스케줄러는 다음번 실행할 스레드에서 해당 스레드를 무시하고 선택한다

대기 대신 연속을 전달

대기를 사용하면 스레드를 스케줄링하기가 쉽지가 않다

함수형 프로그래밍(functional programming)에서 자주 사용하는 ‘나중에 할 일을 전달하는 방식 CPS(continuation passing style)’을 사용할 수 있다

이벤트가 일어나기를 기다리는 대신에, 남은 계산을 클로저 형태로 묶어서 API 로 전달하고, 이 API 는 대기 조건이 만족되었을 때 해당 연산이 다시 실행되기를 보장해준다

정리

2장에서는 대략적인 내용을 많이 알아봤으니 더 자세한 내용은 다음 장들에서 나올 것이다