´ë¸Þ´º ¹Ù·Î°¡±â º»¹® ¹Ù·Î°¡±â

µ¥ÀÌÅÍ ±â¼ú ÀÚ·á

µ¥ÀÌÅÍ ±â¼ú ÀÚ·á »ó¼¼º¸±â
Á¦¸ñ ÇÔ¼öÇü ÇÁ·Î±×·¡¹Ö F# : ¿¹Ãø °¡´ÉÇÑ ¼öÇÐÀû ÇÔ¼ö ±¸ÇöÇϱâ
µî·ÏÀÏ Á¶È¸¼ö 3892
÷ºÎÆÄÀÏ  

ÇÔ¼öÇü ÇÁ·Î±×·¡¹Ö F#

¿¹Ãø °¡´ÉÇÑ ¼öÇÐÀû ÇÔ¼ö ±¸ÇöÇϱâ



¡®º¯°æ ºÒ°¡´É¼º(Immutability)¡¯Àº ÇÔ¼öÇü ¾ð¾îÀÇ ÇÙ½É °³³äÀÌ´Ù. µ¥ÀÌÅ͸¦ º¯°æÇÏÁö ¾ÊÀ½À¸·Î½á ¸í·ÉÇü ¾ð¾î¿¡ ºñÇØ Á» ´õ ½±°Ô ¿¹Ãø °¡´ÉÇÑ ÇÔ¼ö¸¦ ±¸ÇöÇÒ ¼ö ÀÖ´Ù. À̹ø ½Ã°£¿¡´Â ÂüÁ¶ Åõ¸í¼º(Referential Transparency)°ú ºÎÀÛ¿ë(Side-Effect) ±×¸®°í ¼ø¼ö ÇÔ¼ö(Pure Function)ÀÇ °³³äÀ» »ìÆìº¸°í, ¼öÇÐÀû ÇÔ¼ö·Î ÀÎÇÑ ÃÖÀûÈ­¿Í µ¥ÀÌÅÍ ·¹À̽º, µ¥µå¶ôÀ» Á¦°ÅÇÏ´Â ¹æ¹ýÀ» ¼Ò°³ÇÑ´Ù.



¿¹Ãø °¡´ÉÇÑ(Predictable) Äڵ带 ÀÛ¼ºÇÑ´Ù´Â °ÍÀº ¹«¾ùÀ» ÀǹÌÇÒ±î? ±×¸®°í ¿Ö ÇÊ¿äÇÒ±î? ±¹¸³±¹¾î¿ø Ç¥Áر¹¾î´ë»çÀüÀº ¡®¿¹Ãø(çãö´)¡¯ÀÇ ¶æÀ» ¡°¹Ì¸® Çì¾Æ·Á ÁüÀÛÇÔ¡±À̶ó°í ±â¼úÇϰí ÀÖ´Ù. ºñ½ÁÇÑ ¸»·Î´Â ¿¹»ó, ¿¹°ß, ÁüÀÛ, ÃßÃø, Ã߸®, Áö·¹ÁüÀÛ µîÀÌ ÀÖ´Ù. ¿ì¼± Áß¾Ó Ã³¸® ÀåÄ¡(Central Processing Unit, ÀÌÇÏ CPU)ÀÇ ÀÔÀå¿¡¼­ ¿¹ÃøÀ» »ìÆìº¸ÀÚ. CPU¿¡´Â ¿¹Ãø°ú °ü·ÃµÈ ´ëÇ¥ÀûÀÎ ±â¼ú Áß Çϳª·Î ¡®ºÐ±â ¿¹Ãø(Branch Prediction)¡¯ÀÌ ÀÖ´Ù. ¿ä¾àÇÏ¸é ºÐ±â ¿¹ÃøÀº ´ÙÀ½ ºÐ±â¸¦ ¿¹ÃøÇÏ´Â °ÍÀÌ´Ù. ¿Ö ´ÙÀ½ ºÐ±â¸¦ ¿¹ÃøÇÒ±î? ÀÌ Áú¹®¿¡ ´ëÇÑ ´äº¯Àº ¸Å¿ì ´Ü¼øÇÏ´Ù. CPUÀÇ °í¼ÓÈ­¸¦ À§Çؼ­´Ù.

CPU¿¡´Â Á¶°Ç ºÐ±â ¸í·ÉÀÌ Àִµ¥, ÀÌ´Â ºÐ±â·Î ÀÎÇØ ¸í·É¾î ÆÄÀÌÇÁ¶óÀÎÀÌ ÀϽÃÀûÀ¸·Î Á¤ÁöµÇ´Â »óȲÀ» Á¦°ÅÇÔÀ¸·Î½á ¼º´ÉÀ» Çâ»óÇÏ´Â °ÍÀÌ´Ù. ºÐ±â ¿¹ÃøÀº ÇØ´ç Á¶°Ç ºÐ±â¹®ÀÌ ºÐ±âÇÑ ÀÌ·ÂÀ̳ª ·çÇÁ ±¸Á¶, ÇÔ¼ö ±¸Á¶ µîÀÇ Á¤º¸¸¦ ±â¹ÝÀ¸·Î ¼öÇàÇÑ´Ù. ±× ¶§¹®¿¡ ºÐ±â ¿¹ÃøÀÌ Ç×»ó Á¤È®ÇÑ °ÍÀº ¾Æ´Ï´Ù. À̹ø¿¡´Â ÄÚµå ÀÔÀå¿¡¼­ ¿¹ÃøÀ» »ìÆìº¸ÀÚ.



<¸®½ºÆ® 1> PlusOne ÇÔ¼ö int PlusOne(int value) { return value + 1; }



<¸®½ºÆ® 1>°ú °°ÀÌ PlusOneÀÌ ±¸ÇöµÅ ÀÖ´Ù¸é PlusOne ÇÔ¼öÀÇ ÀÔ·Â °ª value°¡ 2015ÀÏ ¶§ È£Ãâ ¼ø¼­³ª ¿ÜºÎ ȯ°æ°ú »ó°ü¾øÀÌ Ç×»ó °°Àº °á°ú °ª 2016À» µ¹·ÁÁÖ°Ô µÈ´Ù. ÀÌ·¯ÇÑ ¼ºÁúÀ» ´ÙÀ½°ú °°ÀÌ Á¤ÀÇÇÒ ¼ö ÀÖ´Ù.

¡°ÇÔ¼öÀÇ ÀÎÀÚ value °ªÀÌ º¯ÇÏÁö ¾Ê´Â´Ù¸é PlusOne ÇÔ¼ö´Â Ç×»ó °°Àº °á°ú¸¦ µ¹·ÁÁØ´Ù.¡±

ÀÌ Á¤ÀǸ¦ Ȱ¿ëÇÏ¸é ¡®PlusOne(2015) = PlusOne(2000 + 15) = 2016¡¯ÀÌ ¼º¸³ÇÏ°Ô µÈ´Ù. value·Î Àü´ÞµÈ °ª 2015¿Í 2000 + 15°¡ °°±â ¶§¹®ÀÌ´Ù. À̸¦ Á» ´õ Ȱ¿ëÇÏ¸é ¡®(PlusOne(2015) - PlusOne (2015)) = 0¡¯µµ ¿ª½Ã ¼º¸³ÇÏ°Ô µÈ´Ù.



<¸®½ºÆ® 2> ÃÖÀûÈ­ Àü int result = PlusOne(x) + PlusOne(x) * (PlusOne(x) ? PlusOne(x));



<¸®½ºÆ® 2>¸¦ ¾Õ¼­ »ìÆìº» °³³äÀ¸·Î Á¢±ÙÇØ º¸ÀÚ. ¿ì¼± ¡®int result = PlusOne(x) + PlusOne(x) * (PlusOne(x) - PlusOne(x));¡¯¿¡¼­ ¡®PlusOne(x) = PlusOne(x)¡¯°¡ ¼º¸³ÇÑ´Ù¸é ¡®PlusOne(x) ? PlusOne(x)¡¯´Â 0ÀÌ µÉ °ÍÀÌ´Ù. ±×·¸´Ù¸é ¡¯PlusOne(x) ? PlusOne(x)¡¯¸¦ ¡®0¡¯À¸·Î ¹Ù²ãµµ Àüü ±¸¹®¿¡ Àǹ̰¡ º¯ÇÏÁö ¾Ê´Â´Ù. ÀÌÁ¦ Àüü ±¸¹®Àº ¡®int result = PlusOne(x) + PlusOne(x) * (0);¡¯À¸·Î º¯°æÇÒ ¼ö ÀÖ´Ù.

º¯°æµÈ Äڵ忡¼­ ¡®PlusOne(x) * (0)¡¯Àº ¼öÇÐÀû »ó½ÄÀ» °í·ÁÇÒ ¶§ ´ç¿¬È÷ 0À¸·Î º¯°æÇÒ ¼ö ÀÖ´Ù. À̸¦ ´Ù½Ã Àû¿ëÇÏ¸é ¡®int result = PlusOne(x) + 0;¡¯ÀÌ µÈ´Ù. °á±¹ ¡®int result = PlusOne(x);¡¯·Î º¯°æÇÒ ¼ö ÀÖ´Ù. Áö±Ý±îÁöÀÇ ÄÚµå º¯°æ °úÁ¤À» Á¤¸®Çϸé <¸®½ºÆ® 3>°ú °°´Ù.



<¸®½ºÆ® 3> ÃÖÀûÈ­ ÈÄ int result = PlusOne(x) + PlusOne(x) * (PlusOne(x) ? PlusOne(x)); ¡æ int result = PlusOne(x) + PlusOne(x) * (0); ¡æ int result = PlusOne(x) + 0; ¡æ int result = PlusOne(x);



Äڵ带 º¯°æÇÏ´Â °úÁ¤À» ÅëÇØ Çʼö Á¶°ÇÀº ÀÔ·Â °ª x¿¡ °°Àº °ªÀ» ÁÖ¸é PlusOne °á°ú °ª ¿ª½Ã Ç×»ó °°´Ù´Â °ÍÀ» ¾Ë ¼ö ÀÖ´Ù. À̸¦ ¹ÙÅÁÀ¸·Î ¡®PlusOne(x) = PlusOne(x)¡¯°¡ ¼º¸³ÇÏ°Ô µÇ¸ç, ¡®PlusOne(x) - PlusOne(x)¡¯°¡ 0À¸·Î ´ëüµÉ ¼ö ÀÖ´Ù. ÀÌ´Â ¡®(PlusOne(x) - PlusOne(x)) = 0¡¯ÀÌ ¼º¸³Çϱ⠶§¹®¿¡ °¡´ÉÇÑ °ÍÀÌ´Ù. ±× ÀÌÈÄ Äڵ带 º¯°æÇÏ´Â °úÁ¤¿¡¼­µµ ¿ª½Ã ¡®(PlusOne(x) * 0) = 0¡¯°ú ¡®(PlusOne(x) + 0) = PlusOne(x)¡¯°¡ ¼º¸³ÇÑ´Ù. ÀÌ´Â ¼öÇÐÀû »ó½Ä¿¡ ±â¹ÝÀ» µÎ°í Äڵ带 ÃÖÀûÈ­ÇÒ ¼ö ÀÖÀ½À» ÀǹÌÇÑ´Ù. Á» ´õ °£´ÜÇÑ ¿¹Á¦ <¸®½ºÆ® 4>¸¦ ÅëÇØ ´Ù¸¥ ÃÖÀûÈ­ ¹æ¹ýÀ» »ìÆìº¸ÀÚ.



<¸®½ºÆ® 4> ÃÖÀûÈ­ Àü int result = PlusOne(x) * PlusOne(x)



<¸®½ºÆ® 4>ÀÇ ÄÚµå´Â ÇöÀç PlusOneÀ» Áߺ¹ È£ÃâÇϰí ÀÖ´Ù. PlusOneÀÌ ¡®value¿¡ °°Àº °ªÀ» ÁÖ¸é PlusOne ÇÔ¼ö´Â Ç×»ó °°Àº °á°ú¸¦ µ¹·ÁÁش١¯¸¦ ÁؼöÇϰí ÀÖ´Ù¸é PlusOne(x)¸¦ ÇÑ ¹ø¸¸ È£ÃâÇØ ±× °á°ú¸¦ ÀúÀåÇÑ ÈÄ temp º¯¼ö·Î µ¿ÀÏÇÑ °á°ú¸¦ ¾òÀ» ¼ö ÀÖ´Ù. À̹ø ¿¹Á¦´Â ÇÔ¼ö¸¦ º¯¼ö·Î ´ëÃ¼ÇØ °á°ú °ªÀ» ÀúÀåÇϰí Áߺ¹ È£Ãâ ¾øÀÌ PlusOne ÇÔ¼ö¸¦ ÇÑ ¹øÀÇ È£Ãâ·Î ÃÖÀûÈ­ÇÑ´Ù.



<¸®½ºÆ® 5> ÃÖÀûÈ­ ÈÄ int temp = PlusOne(x); int result = temp * temp;



µÎ °¡Áö ÃÖÀûÈ­ ¿¹Á¦ ¸ðµÎ PlusOne(x) ÇÔ¼öÀÇ °á°ú °ªÀ» ¿¹ÃøÇÒ ¼ö ÀÖ¾ú´Ù. ÀÌ´Â CPU¿¡¼­ Á¦°øÇÏ´Â ºÐ±â ¿¹Ãø°ú ´Þ¸® ¿¹Ãø ½ÇÆÐ°¡ Á¸ÀçÇÏÁö ¾Ê±â ¶§¹®¿¡ °¡´ÉÇÑ °ÍÀÌ´Ù. ÀÌ·¯ÇÑ ¼ºÁú ´öºÐ¿¡ ¨çÇÔ¼ö¸¦ °ªÀ̳ª º¯¼ö·Î ¹Ù²ãµµ Àüü ±¸¹®¿¡ Àǹ̰¡ º¯ÇÏÁö ¾ÊÀ¸¸ç, ¨è¼öÇÐÀûÀ¸·Î ÃÖÀûÈ­ÇÒ ¼ö ÀÖ´Ù. ÀÌ¿Í °°Àº ¼ºÁúÀ» °®´Â ÇÔ¼ö¸¦ ÂüÁ¶¿¡ Åõ¸í(Referentially Transparent)ÇÑ ÇÔ¼ö¶ó°í ÇÑ´Ù. ¶ÇÇÑ ÂüÁ¶¿¡ Åõ¸íÇÑ ÇÔ¼ö´Â ÀÔ·Â °ª¿¡ µû¶ó °á°ú °ªÀÌ °áÁ¤µÇ±â ¶§¹®¿¡ °áÁ¤ ÇÔ¼ö(Deterministic Function)¶ó°í ÇÑ´Ù. ÀÌ¿¡ »ó¹ÝµÇ´Â °³³äÀº ÂüÁ¶¿¡ ºÒÅõ¸í(Referentially Opaque)ÇÑ ÇÔ¼ö ¶Ç´Â ºñ°áÁ¤ ÇÔ¼ö(Non-deterministic Function)¶ó ÇÑ´Ù. ÂüÁ¶¿¡ ºÒÅõ¸íÇÑ ÇÔ¼ö´Â µ¿ÀÏÇÑ ÀÔ·Â °ª¿¡µµ ºÒ±¸ÇÏ°í °á°ú °ªÀÌ Ç×»ó °°Áö ¾Ê´Ù.

<¸®½ºÆ® 6>¿¡ ±¸ÇöµÈ PlusOne ÇÔ¼ö´Â ÇÔ¼öÀÇ ¿ÜºÎ globalValue º¯¼ö °ªÀ» º¯°æ½ÃŲ(write) ÈÄ¿¡ ÇØ´ç º¯¼öÀÇ °ªÀ» ÂüÁ¶ÇØ °á°ú °ªÀ» ¸¸µç´Ù.



<¸®½ºÆ® 6> ÂüÁ¶¿¡ ºÒÅõ¸íÇÑ ÇÔ¼ö int globalValue = 0; int PlusOne(int value) { globalValue = globalValue + 1; return value + globalValue; }



±× ¶§¹®¿¡ ÀÔ·Â °ª value°¡ °°¾Æµµ °á°ú °ªÀÌ Ç×»ó °°Áö ¾Ê°Ô µÈ´Ù. ¿¹¸¦ µé¾î, PlusOne(2015)¸¦ Áߺ¹Çؼ­ È£ÃâÇϸé óÀ½¿¡´Â 2016, ´ÙÀ½¿¡´Â 2017À» µ¹·ÁÁÖ°Ô µÈ´Ù. ÀÌ´Â ¡®PlusOne(2015) = PlusOne(2015)¡¯°¡ ¼º¸³µÉ ¼ö ¾ø°Ô µÈ´Ù. ¶ÇÇÑ ¡®((PlusOne(2015) - PlusOne(2015)) = 0¡¯ ¿ª½Ã ¼º¸³ÇÏÁö ¾Ê´Â´Ù.

À̸¦ ÅëÇØ ¾Õ¿¡¼­ »ìÆìº» ÃÖÀûÈ­ ¿¹Á¦ 2°³ ¸ðµÎ <¸®½ºÆ® 6>¿¡¼­ ±¸ÇöÇÑ ÂüÁ¶¿¡ ºÒÅõ¸íÇÑ ÇÔ¼ö PlusOneÀ̶ó¸é ÇØ´ç »çÇ×ÀÌ ¾øÀ» °ÍÀÌ´Ù. ÂüÁ¶¿¡ ºÒÅõ¸íÇÑ ÇÔ¼ö´Â ÃÖÀûÈ­ÇÒ ±âȸ ÀÚü°¡ ÁÙ¾îµé °ÍÀ̸ç, ¼öÇÐÀûÀ¸·Î ÇÔ¼ö¸¦ ´Ù·ç±âµµ ¾î·Æ´Ù. ¶Ç ÇÔ¼ö ¿ÜºÎ µ¥ÀÌÅ͸¦ º¯°æ½ÃŰ¹Ç·Î ÀǵµÇÏÁö ¾Ê´Â °á°ú¸¦ ¹ß»ý½Ãų ¼ö ÀÖ´Ù. ÀÌó·³ ÇÔ¼ö °á°ú °ª ¿Ü¿¡ ÇÔ¼ö ¿ÜºÎ¿¡ ÀÖ´Â »óŸ¦ º¯°æ½ÃŰ´Â °É ¡®ºÎÀÛ¿ë(Side-Effect)ÀÌ ÀÖ´Ù¡¯°í ¸»ÇÑ´Ù. ºÎÀÛ¿ëÀº ´ÙÀ½°ú °°ÀÌ ´ëÇ¥ÀûÀ¸·Î ³× °¡Áö °æ¿ì¿¡ ¹ß»ýÇÑ´Ù.

1. ÇÔ¼ö ¿ÜºÎÀÇ º¯¼ö¸¦ º¯°æ½Ãų ¶§(¿¹: Àü¿ª º¯¼ö, Á¤Àû º¯¼ö)
2. ÇÔ¼ö ÀÎÀÚ¸¦ º¯°æÇÒ ¶§(¿¹: ÂüÁ¶ º¯¼ö)
3. È­¸éÀ̳ª ÆÄÀÏ¿¡ µ¥ÀÌÅ͸¦ ¾µ ¶§
4. ´Ù¸¥ ºÎÀÛ¿ëÀÌ ÀÖ´Â ÇÔ¼öÀÇ °á°ú¸¦ ÂüÁ¶ÇÒ ¶§

ÂüÁ¶¿¡ ºÒÅõ¸íÇÑ ÇÔ¼ö´Â ´ë°³ ºÎÀÛ¿ëÀÌ ÀÖ´Ù. ±×·¯³ª ºÎÀÛ¿ëÀÌ ¾ø´Ù°í ÇØ¼­ ¸ðµÎ ÂüÁ¶¿¡ Åõ¸íÇÑ ÇÔ¼ö´Â ¾Æ´Ï´Ù. °¡·É ÇöÀç ½Ã°£À» µ¹·ÁÁÖ´Â ÇÔ¼ö°¡ ÀÖ´Ù°í °¡Á¤ÇØ º¸ÀÚ. ÇØ´ç ÇÔ¼ö´Â ¿ÜºÎ µ¥ÀÌÅ͸¦ ÀüÇô º¯°æ½ÃŰÁö ¾ÊÁö¸¸(ºÎÀÛ¿ëÀÌ ¾øÁö¸¸) ÇØ´ç ÇÔ¼ö¸¦ È£ÃâÇÒ ¶§¸¶´Ù ½Ã°£À̶ó´Â Àü¿ª »óÅ º¯°æÀ¸·Î ÀÎÇØ ¸Å¹ø ´Ù¸¥ °á°ú °ªÀ» µ¹·ÁÁÙ °ÍÀÌ´Ù.

C#, Java, C++¿Í °°Àº ¸í·ÉÇü ¾ð¾îµéÀº ºÎÀÛ¿ëÀ» »ç¿ëÇØ ÇÁ·Î±×·¡¹ÖÇÏ°Ô µÈ´Ù. ±×·¯³ª F#, Scala, Haskell°ú °°Àº ÇÔ¼öÇü ¾ð¾îµéÀº ºÎÀÛ¿ëÀ» ÃÖ¼ÒÈ­ÇØ ÇÁ·Î±×·¡¹ÖÇÑ´Ù. ƯÈ÷ ÇÔ¼öÇü ¾ð¾î¿¡¼­´Â ºÎÀÛ¿ëÀÌ ¾øÀ¸¸ç, ÂüÁ¶¿¡ Åõ¸íÇÑ ÇÔ¼ö¸¦ ¡®¼ø¼ö ÇÔ¼ö(Pure Function)¡¯¶ó°í ÇÑ´Ù. ¼ø¼ö ÇÔ¼ö´Â ÂüÁ¶¿¡ Åõ¸íÇϹǷΠ¿¹ÃøÀÌ °¡´ÉÇÏ´Ù. ±× ´öºÐ¿¡ ÇÔ¼ö¸¦ °ªÀ̳ª º¯¼ö·Î ´ëüÇÒ ¼ö ÀÖ¾î ¸í·ÉÇü ÇÁ·Î±×·¡¹Öº¸´Ù ´õ ¸¹Àº ÃÖÀûÈ­ ±âȸ¸¦ ¾òÀ» ¼ö ÀÖ´Ù. ¶Ç ÇÔ¼ö¸¦ ¼öÇÐÀûÀ¸·Î ´Ù·ê ¼ö ÀÖ¾î Á¤È®¼º Áõ¸íÀÌ ½¬¿öÁüÀ¸·Î Å×½ºÆ®¿Í µð¹ö±ë¿¡µµ ¿ëÀÌÇÏ´Ù.

ºÎÀÛ¿ëÀÌ ¾øÀ¸¹Ç·Î ÇÔ¼ö ³»ºÎ ±¸Çö¿¡¼­ ÇÊ¿äÇÑ Áö¿ª º¯¼ö¿Í °á°ú °ª ¿Ü¿¡´Â ¿ÜºÎ¿¡ ¿µÇâÀ» ÁÖÁö ¾Ê´Â´Ù. ÀÌ´Â ÇÔ¼ö°¡ ¿øÀÚÀû(Atomic) ¼ºÁúÀ» °®°Ô µÅ ƯÁ¤ ±â´ÉÀ» ÇÔ¼ö ´ÜÀ§·Î ¿Ï·áÇϵµ·Ï ¼³°è¸¦ °­¿äÇÑ´Ù. ÀÌ´Â ÄÚµåÀÇ °¡µ¶¼º°ú À¯Áö °ü¸® ÆíÀǼºÀ» Çâ»óÇÏ°Ô µÈ´Ù. °´Ã¼ÁöÇâ ¼³°è ¿øÄ¢¿¡¼­ ¹Ù¶óº¸¸é ÇÔ¼ö ´ÜÀ§·Î ´ÜÀÏ Ã¥ÀÓ ¿øÄ¢(SRP, Single Responsibility Principle)À» ÁؼöÇÏ°Ô µÈ´Ù. ÇÔ¼öÇü ¾ð¾î´Â ÀÌ·¯ÇÑ ¼ºÁúÀ» °®´Â ÇÔ¼ö ±¸ÇöÀ» ¾ð¾î Â÷¿ø¿¡¼­ Áö¿øÇÏ°í °­¿äÇÑ´Ù.

ÀÌ ¸ðµç Ư¡Àº ¿¹Ãø °¡´ÉÇÑ ÇÔ¼ö·ÎºÎÅÍ ½ÃÀ۵ƴÙ. ¿¹Ãø °¡´ÉÇÑ ÇÔ¼ö¸¦ ¸¸µé±â À§Çؼ­´Â ºÎÀÛ¿ëÀÌ Á¸ÀçÇØ¼­´Â ¾È µÈ´Ù. ºÎÀÛ¿ëÀº °á±¹ ¿ÜºÎ¿¡ ¿µÇâÀ» ÁÖÁö ¾Ê´Â °ÍÀÌ´Ù. À̸¦ Á» ´õ ±¸Ã¼ÀûÀ¸·Î ÄÚµå ¼öÁØÀ¸·Î ÇØ¼®Çϸé, ÇÑ ¹ø ÃʱâÈ­µÈ µ¥ÀÌÅÍ´Â º¯°æ½ÃŰÁö ¾Ê´Â °ÍÀÌ´Ù. ÀÌ·¯ÇÑ Æ¯Â¡¿¡ ±âÀÎÇØ ÇÔ¼öÇü ¾ð¾î´Â º¯°æ ºÒ°¡´É¼º(Immutability)À̶ó´Â ÇÁ·Î±×·¡¹Ö ¸ðµ¨¿¡ »Ñ¸®¸¦ µÎ°í ÀÖ´Ù. ÀÌ·ÐÀûÀ¸·Î´Â ¶÷´Ù ´ë¼ö¿¡ ±Ù°£À» µÎ°í ÀÖ´Ù.

2005³â ÀÌÈÄ ¸ÖƼ ÄÚ¾î·Î CPU°¡ ¹ßÀüÇϸ鼭 º´·Ä ÇÁ·Î±×·¡¹ÖÀº Çʼö »çÇ×ÀÌ µÇ°í ÀÖ´Ù. ±×·¯³ª ´Ù¾çÇÑ º´·Ä ¶óÀ̺귯¸®¸¦ »ç¿ëÇÏ´Â °Í¸¸À¸·Î ¼º´É Çâ»óÀ» ±â´ëÇϱⰡ ½±Áö ¾Ê´Ù. ±× ÀÌÀ¯´Â º´·Ä ¶óÀ̺귯¸®ÀÇ ¹®Á¦¶ó±âº¸´Ù´Â ¸í·ÉÇü ¾ð¾î°¡ ÁöÇâÇϰí ÀÖ´Â µ¥ÀÌÅÍ º¯°æ ÇÁ·Î±×·¡¹Ö ¸ðµ¨¿¡ ÀÖ´Ù. µ¥ÀÌÅÍ º¯°æÀ¸·Î ÀÎÇÑ ´ëÇ¥ÀûÀÎ ¹®Á¦ Áß Çϳª´Â µ¥ÀÌÅÍ ·¹À̽º(Data Race)´Ù.



<¸®½ºÆ® 7> µ¥ÀÌÅÍ ·¹À̽º using System; using System.Threading; namespace DataRace { class Program { static int count = 0; static void Main() { Thread workThread1 = new Thread(WorkThreadJob1); Thread workThread2 = new Thread(WorkThreadJob2); workThread1.Start(); workThread2.Start(); workThread1.Join(); workThread2.Join(); Console.WriteLine ("Final count: {0}", count); } static void WorkThreadJob1() { for (int i = 0; i < 5; i++) { count++; } } static void WorkThreadJob2() { for (int i = 0; i < 5; i++) { count++; } } } }



<¸®½ºÆ® 7>ÀÇ Äڵ带 ÄÄÆÄÀÏÇØ ½ÇÇàÇÏ¸é °á°ú °ªÀÌ Ç×»ó °°Áö ¾Ê´Ù. WorkThreadJob1 ÇÔ¼ö¿Í WorkThreadJob2 ÇÔ¼ö¿¡ ºÎÀÛ¿ëÀÌ Àֱ⠶§¹®ÀÌ´Ù. ±×·¯¹Ç·Î ÂüÁ¶¿¡ Åõ¸íÇÏÁöµµ ¾Ê´Ù. <¸®½ºÆ® 7>ÀÇ µ¥ÀÌÅÍ ·¹À̽º ¹ß»ý °úÁ¤À» Á» ´õ ÀÚ¼¼È÷ È®ÀÎÇϱâ À§ÇØ Sleep ÇÔ¼ö¿Í È­¸é Ãâ·Â ÇÔ¼ö¸¦ Ãß°¡ÇÑ ÈÄ µ¥ÀÌÅÍ ·¹À̽º ¹ß»ý °úÁ¤À» »ìÆìº¸ÀÚ(<¸®½ºÆ® 8> ÂüÁ¶).



<¸®½ºÆ® 8> µ¥ÀÌÅÍ ·¹À̽º ¹ß»ý °úÁ¤ È®ÀÎÇϱâ using System; using System.Threading; namespace DataRace_in_Detail { class Program { static int count = 0; static void Main() { Console.WriteLine("Work Thread1 Work Thread2"); Console.WriteLine("========================================================="); Thread workThread1 = new Thread(WorkThreadJob1); Thread workThread2 = new Thread(WorkThreadJob2); workThread1.Start(); workThread2.Start(); workThread1.Join(); workThread2.Join(); Console.WriteLine("========================================================="); Console.WriteLine("Final count: {0} ", count); } static void WorkThreadJob1() { for (int i = 0; i < 5; i++) { int tmp = count; Console.WriteLine("Read count={0}", tmp); Thread.Sleep(50); tmp++; Console.WriteLine("Incremented tmp to {0}", tmp); Thread.Sleep(20); count = tmp; Console.WriteLine("Written count={0}", tmp); Thread.Sleep(30); } } static void WorkThreadJob2() { for (int i = 0; i < 5; i++) { int tmp = count; Console.WriteLine(" Read count={0}", tmp); Thread.Sleep(20); tmp++; Console.WriteLine(" Incremented tmp to {0}", tmp); Thread.Sleep(10); count = tmp; Console.WriteLine(" Written count={0}", tmp); Thread.Sleep(40); } } } }





<¸®½ºÆ® 8>ÀÇ Äڵ带 ÄÄÆÄÀÏÇØ ½ÇÇàÇϸé <±×¸² 1>°ú °°ÀÌ µ¥ÀÌÅÍ ·¹À̽º ¹ß»ý °úÁ¤À» È®ÀÎÇÒ ¼ö ÀÖ´Ù. ¡®Work Thread1¡¯¿¡¼­ count °ªÀ» ÀÐÀº ÈÄ¿¡ ¡®Work Thread2¡¯¿¡¼­µµ count °ªÀ» ÀÐ°í ¿¬»ê(count++) ÈÄ count °ªÀ» º¯°æ½ÃŲ´Ù. ±×·¯³ª ¡®Work Thread1¡¯¿¡¼­´Â ¡®Work Thread2¡¯¿¡¼­ º¯°æµÈ count °ªÀ» È®ÀÎÇÏÁö ¸øÇϰí ÀÌÀü °ªÀ¸·Î ¿¬»êÇØ count °ªÀ» º¯°æ½Ã۱⠶§¹®¿¡ ¿Ã¹Ù¸¥ count °ªÀ» ¾òÁö ¸øÇÑ´Ù. ±× °á°ú ¡®6¡¯À̶ó´Â À߸øµÈ °á°ú µ¥ÀÌÅ͸¦ ¾ò°Ô µÈ´Ù.

ÀÌ·¯ÇÑ µ¥ÀÌÅÍ ·¹À̽º ¹®Á¦´Â µ¿±âÈ­¸¦ ÅëÇØ ÇØ°áÇÒ ¼ö ÀÖ´Ù. µ¿±âÈ­¸¦ ±¸ÇöÇÏ´Â ¹æ¹ýÀº ¸Å¿ì ´Ù¾çÇÏ´Ù. <¸®½ºÆ® 9>´Â C#¿¡¼­ Á¦°øÇÏ´Â lock Ű¿öµå¸¦ »ç¿ëÇØ µ¥ÀÌÅÍ ·¹À̽º¸¦ ÇØ°áÇÑ´Ù. lockÀº C#¿¡¼­ Á¦°øÇϴ Ű¿öµå °¡¿îµ¥ °¡Àå ½±°Ô µ¿±âÈ­¸¦ ±¸ÇöÇÏ´Â ¹æ¹ýÀÌ´Ù. lock Ű¿öµå ºí·ÏÀº ÀÓ°è ¿µ¿ª(Critical Section)À¸·Î 󸮵Ǹç, ƯÁ¤ ½º·¹µå°¡ ÀÓ°è ¿µ¿ª¿¡ ÀÖ´Â µ¿¾È ´Ù¸¥ ½º·¹µå´Â lock ºí·ÏÀÌ ÇØÁ¦µÉ ¶§±îÁö ´ë±âÇÑ´Ù. lock ºí·ÏÀÌ ÇØÁ¦µÇ¸é ´Ù¸¥ ½º·¹µå°¡ ÀÓ°è ¿µ¿ª¿¡ µé¾î°¥ ¼ö ÀÖ°Ô µÈ´Ù. À̸¦ ÅëÇØ µ¥ÀÌÅÍ º¯°æ°ú °ü·ÃµÈ ÀÛ¾÷À» »óÈ£ ¹èŸÀû(Mutual-Exclusion)À¸·Î ¼öÇàÇÒ ¼ö ÀÖ´Ù.



<¸®½ºÆ® 9> µ¿±âÈ­¸¦ ÅëÇÑ µ¥ÀÌÅÍ ·¹À̽º Á¦°ÅÇϱâ using System; using System.Threading; namespace DataRace_Solution_byLock { public class Test { static int count = 0; static readonly object countLock = new object(); static void Main() { Console.WriteLine("Work Thread1 Work Thread2"); Console.WriteLine("========================================================="); Thread workThread1 = new Thread(WorkThreadJob1); Thread workThread2 = new Thread(WorkThreadJob2); workThread1.Start(); workThread2.Start(); workThread1.Join(); workThread2.Join(); Console.WriteLine("========================================================="); Console.WriteLine("Final count: {0} ", count); Console.WriteLine("Final count: {0}", count); } static void WorkThreadJob1() { for (int i = 0; i < 5; i++) { lock (countLock) { int tmp = count; Console.WriteLine("Read count={0}", tmp); Thread.Sleep(50); tmp++; Console.WriteLine("Incremented tmp to {0}", tmp); Thread.Sleep(20); count = tmp; Console.WriteLine("Written count={0}", tmp); } Thread.Sleep(30); } } static void WorkThreadJob2() { for (int i = 0; i < 5; i++) { lock (countLock) { int tmp = count; Console.WriteLine(" Read count={0}", tmp); Thread.Sleep(20); tmp++; Console.WriteLine(" Incremented tmp to {0}", tmp); Thread.Sleep(10); count = tmp; Console.WriteLine(" Written count={0}", tmp); } Thread.Sleep(40); } } } }



<¸®½ºÆ® 9>ÀÇ ½ÇÇà °á°ú´Â <±×¸² 2>¿Í °°ÀÌ µ¥ÀÌÅÍ ·¹À̽º°¡ Á¦°ÅµÅ Ç×»ó °°Àº °á°ú °ªÀ» ¾ò°Ô µÈ´Ù. WorkThreadJob1°ú WorkThreadJob2 ÇÔ¼ö´Â ºÎÀÛ¿ëÀÌ ±Ùº»ÀûÀ¸·Î Á¸ÀçÇÏÁö¸¸, À̸¦ µ¿±âÈ­·Î Á¦°ÅÇϰí ÀÖ´Ù.

ºÎÀÛ¿ëÀº µ¿±âÈ­¸¦ ÅëÇØ Á¦°ÅÇÒ ¼ö ÀÖÁö¸¸, ÀÌ·¯ÇÑ µ¥ÀÌÅÍ ·¹À̽º ¹®Á¦´Â ¹ß°ßÇϱ⵵ ÀçÇöÇϱ⵵ ½±Áö ¾Ê´Ù.

´õ¿íÀÌ µ¿±âÈ­·Î ÀÎÇØ ¼º´É Çâ»ó¿¡ Å« Àå¾Ö¸¦ ÃÊ·¡ÇÏ´Â °æ¿ìµµ ¸¹´Ù. ±×·¡¼­ ´ëºÎºÐ º´·Ä ¶óÀ̺귯¸®µéÀº ¼º´É Àå¾Ö¸¦ ÃÖ¼ÒÈ­´Â ¾ÆÁÖ °¡º­¿î µ¿±âÈ­ ±â´ÉÀ» Á¦°øÇÑ´Ù. ¶Ç µ¿±âÈ­·Î ÀÎÇØ µ¥µå¶ô(Deadlock)À̶ó´Â »õ·Î¿î ¹®Á¦°¡ ¹ß»ýÇÒ ¼ö ÀÖ´Ù. µ¥µå¶ôÀº ¡®2°³ ÀÌ»óÀÇ ÀÛ¾÷ÀÌ »ó´ë¹æÀÇ ÀÛ¾÷ÀÌ ³¡³ª±â¸¸À» ±â´Ù¸®´Ù°¡ °á°úÀûÀ¸·Î ¾Æ¹«°Íµµ ¿Ï·áÇÏÁö ¸øÇÏ´Â »óÅ¡¯¸¦ ÀǹÌÇÑ´Ù. <¸®½ºÆ® 9>¿¡¼­ ºÎÀÛ¿ëÀ» ÇØ°áÇϱâ À§ÇØ »ç¿ëÇÑ µ¿±âÈ­ lock Ű¿öµå·Î µ¥µå¶ôÀÌ ¹ß»ýÇÏ´Â ¿¹¸¦ »ìÆìº¸ÀÚ(<¸®½ºÆ® 10> ÂüÁ¶).



<¸®½ºÆ® 10> µ¥µå¶ô using System; using System.Threading; namespace Deadlock { class Program { private static object objLockA = new Object(); private static object objLockB = new Object(); public static void Main() { Thread workThread1 = new Thread(WorkThreadJob1); Thread workThread2 = new Thread(WorkThreadJob2); workThread1.Start(); workThread2.Start(); workThread1.Join(); workThread2.Join(); Console.WriteLine("Done Processing..."); } private static void WorkThreadJob1() { lock (objLockA) { Console.WriteLine("Trying to acquire lock on ObjLockB"); Thread.Sleep(1000); lock (objLockB) { // µ¥ÀÌÅÍ º¯°æ ¿µ¿ª, ±×·¯³ª Àý´ë È£ÃâµÇÁö ¾Ê´Â´Ù. Console.WriteLine("In WorkThreadJob1 Critical Section."); } } } private static void WorkThreadJob2() { lock (objLockB) { Console.WriteLine("Trying to acquire lock on ObjLockA"); lock (objLockA) { // µ¥ÀÌÅÍ º¯°æ ¿µ¿ª, ±×·¯³ª Àý´ë È£ÃâµÇÁö ¾Ê´Â´Ù. Console.WriteLine("In WorkThreadJob2 Critical Section."); } } } } }





<¸®½ºÆ® 10>À» ½ÇÇàÇϸé <±×¸² 3>°ú °°ÀÌ WorkThreadJob1°ú WorkThreadJob2 ¸ðµÎ ÀÓ°è ¿µ¿ª¿¡ ÇØ´çÇÏ´Â ÄÚµå ºí·ÏÀ» Àý´ë È£ÃâÇÏÁö ¾Ê´Â´Ù. ¼­·Î ÀÓ°è ¿µ¿ªÀÌ ³¡³ª±â¸¸À» ±â´Ù¸®°í ÀÖ¾î °á°úÀûÀ¸·Î ÇÁ·Î±×·¥Àº ´õ´Â ÁøÇàÇÏÁö ¾Ê°Ô µÈ´Ù. µ¥µå¶ôÀº µ¿±âÈ­¸¦ À§ÇØ ÀÓ°è ¿µ¿ªÀ» µé¾î°¡±â À§ÇÑ ¹«ÇÑ ±â´Ù¸²À¸·Î ¹ß»ýÇÑ´Ù. µ¥µå¶ôÀ» ÇØ°áÇϱâ À§Çؼ­´Â ¾î´À ÇÑÂÊ ½º·¹µå¿¡¼­ ±â´Ù¸²À» Æ÷±âÇØ¾ß ÇÑ´Ù. ±Ùº»ÀûÀÎ ÇØ°áÃ¥Àº ´ç¿¬È÷ µ¥µå¶ôÀÌ ¹ß»ýÇÏÁö ¾Êµµ·Ï ÇÏ´Â °ÍÀÌ´Ù. ´ëÇ¥ÀûÀ¸·Î ŸÀ̸Ӹ¦ ÀÌ¿ëÇÏ´Â ¹æ¹ýÀÌ ÀÖ´Ù. ÀÓ°è ¿µ¿ª¿¡ µé¾î°¡±â À§ÇÑ ´ë±â ½Ã°£À» ¼³Á¤ÇÔÀ¸·Î½á ÁöÁ¤ÇÑ ½Ã°£ÀÌ ÃʰúÇÏ¸é ±â´Ù¸²À» Æ÷±âÇϵµ·Ï ÇÏ´Â °ÍÀÌ´Ù.

<¸®½ºÆ® 11>Àº Monitor Ŭ·¡½º¿¡¼­ Á¦°øÇÏ´Â ¡®public static bool TryEnter(object obj, int millisecondsTimeout);¡¯ Á¤Àû ¸â¹ö ÇÔ¼ö¸¦ »ç¿ëÇß´Ù. 5Ãʸ¦ ±â´Ù¸®°í ±× ¾È¿¡ ÀÓ°è ¿µ¿ª¿¡ µé¾î°¥ ¼ö ¾ø´Ù°í ÆÇ´ÜµÇ¸é ÀÓ°è ¿µ¿ª ÁøÀÔÀ» Æ÷±âÇÒ °ÍÀÌ´Ù.



<¸®½ºÆ® 11> ŸÀ̸Ӹ¦ ÅëÇÑ µ¥µå¶ô Á¦°Å using System; using System.Threading; namespace Deaklock_Solution_byTime { class Program { private static object objLockA = new Object(); private static object objLockB = new Object(); public static void Main() { Thread workThread1 = new Thread(WorkThreadJob1); Thread workThread2 = new Thread(WorkThreadJob2); workThread1.Start(); workThread2.Start(); workThread1.Join(); workThread2.Join(); Console.WriteLine("Done Processing..."); } private static void WorkThreadJob1() { lock (objLockA) { Console.WriteLine("Trying to acquire lock on objLockB"); Thread.Sleep(1000); if (Monitor.TryEnter(objLockB, 5000)) { try { Console.WriteLine("In WorkThreadJob1 Critical Section."); } finally { Monitor.Exit(objLockB); } } else { Console.WriteLine("Unable to acquire lock, exiting WorkThreadJob1."); } } } private static void WorkThreadJob2() { lock (objLockB) { Console.WriteLine("Trying to acquire lock on objLockA"); lock (objLockA) { Console.WriteLine("In WorkThreadJob2 Critical Section."); } } } } }



Monitor Ŭ·¡½º¿¡ Á¦°øÇÏ´Â TryEnter ÇÔ¼ö¸¦ ÅëÇØ <±×¸² 4>¿Í °°ÀÌ 5ÃÊ ÀÌÈÄ¿¡´Â WorkThreadJob2 ÇÔ¼ö°¡ ¼öÇàµÇ´Â °ÍÀ» È®ÀÎÇÒ ¼ö ÀÖ´Ù.



µ¥ÀÌÅÍ ·¹À̽º´Â µ¥ÀÌÅÍ º¯°æ ¶§¹®¿¡ ¹ß»ýÇÏ´Â ¹®Á¦À̸ç, À̸¦ ÇØ°áÇϰíÀÚ µ¿±âÈ­¸¦ µµÀÔÇß´Ù. ±×·¯³ª µ¿±âÈ­´Â µ¥µå¶ôÀ̶ó´Â »õ·Î¿î ¹®Á¦¸¦ À¯¹ßÇÒ ¼ö ÀÖ´Ù. ¹®Á¦ ÇØ°á ¹æ¹ýÀÌ »õ·Î¿î ¹®Á¦¸¦ ³º´Â ¿ôÁö ¸øÇÒ »óȲÀÌ ¹ß»ýÇÑ °ÍÀÌ´Ù. ÀÌ·¯ÇÑ ¹®Á¦µéÀº ¸ðµÎ µ¥ÀÌÅÍ º¯°æÀ¸·Î ÀÎÇØ ¹ß»ýÇÑ´Ù. ¸ÖƼ ÄÚ¾î¿Í ¸Å´Ï ÄÚ¾î·Î ¹ßÀüÇÏ´Â °úÁ¤¿¡¼­ µ¥ÀÌÅÍ º¯°æÀ¸·Î ·ÎÁ÷À» ÀÛ¼ºÇÏ°Ô µÇ¸é ÀÌ·¯ÇÑ ¹®Á¦µéÀÌ ´õ¿í ºó¹øÇÏ°Ô »ý°Ü³¯ °ÍÀÌ´Ù. À̸¦ ÇØ°áÇϱâ À§ÇÑ ³ë·Â°ú ½Ã°£ ¿ª½Ã ÇÊ¿äÇÏ´Ù´Â ¾ê±â´Ù. ±×·¸´Ù¸é ÀÌ·¯ÇÑ ¹®Á¦¸¦ ±Ùº»ÀûÀ¸·Î ÇØ°áÇÒ ¼ö ¾øÀ»±î? µ¥ÀÌÅÍ ·¹À̽º¿Í µ¥µå¶ô °°Àº ¹®Á¦°¡ Á¸ÀçÇÒ ¼ö ¾øµµ·Ï ÇÒ ¼ö´Â ¾øÀ»±î? µ¿±âÈ­°¡ ¾ø´Â ÇÁ·Î±×·¡¹Ö ¸ðµ¨Àº ¾øÀ»±î?

ÀÌ·¯ÇÑ ¹®Á¦ÀÇ ±Ùº»ÀûÀÎ ¿øÀÎÀº µ¥ÀÌÅÍ º¯°æÀ¸·ÎºÎÅÍ ¹ß»ýÇÏ´Â ºÎÀÛ¿ëÀÌ´Ù. ÇÔ¼öÇü ¾ð¾î´Â ±âº»ÀûÀ¸·Î µ¥ÀÌÅÍ º¯°æÀ» Á¦°øÇÏÁö ¾Ê±â ¶§¹®¿¡ µ¥ÀÌÅÍ ·¹À̽º ¹®Á¦ ÀÚü°¡ ¹ß»ýÇÏÁö ¾Ê´Â´Ù. µ¿±âÈ­ ¿ª½Ã ÇÔ¼öÇü ¾ð¾î¿¡¼­´Â ÁÖ¿ä °ü½É»ç°¡ µÉ ¼ö ¾ø´Ù. µ¿±âÈ­°¡ Á¦°ÅµÇ±â ¶§¹®¿¡ ÀÚ¿¬½º·´°Ô µ¥µå¶ô ¶ÇÇÑ ÇØ°áµÈ´Ù. ÇÔ¼öÇü ¾ð¾î°¡ ÃÖ±Ù µé¾î ÁÖ¸ñ¹Þ°í ÀÖ´Â ÀÌÀ¯ Áß Çϳª´Ù.

ÇÔ¼öÇü ¾ð¾î¿¡¼­´Â µ¥ÀÌÅÍ º¯°æÀ¸·Î ¹ß»ýÇÏ´Â ¹®Á¦µéÀ» °í¹ÎÇÒ Çʿ䰡 ¾ø´Ù. ±×·¯³ª ÀÌ·¯ÇÑ ÇÁ·Î±×·¡¹Ö ¸ðµ¨ÀÌ ÀåÁ¡¸¸ ÀÖ´Â °Ç ¾Æ´Ï´Ù. µ¥ÀÌÅÍ º¯°æ ºÒ°¡´É¼ºÀ¸·Î ÀÎÇØ ÃÊ·¡ÇÏ´Â ¾Ö·Î »çÇ× ¿ª½Ã Á¸ÀçÇÑ´Ù. ±×·¯³ª µ¥ÀÌÅÍ º¯°æ ºÒ°¡´É¼ºÀ¸·Î ÀÎÇØ ÇÔ¼öÀÇ ÀÎÀÚ °ªÀÌ º¯ÇÏÁö ¾Ê´Â´Ù¸é ÇÔ¼ö´Â Ç×»ó °°Àº °á°ú¸¦ µ¹·ÁÁÖ°í(ÂüÁ¶ Åõ¸í¼º), °á°ú °ª ¿Ü¿¡´Â ÇÔ¼ö ¿ÜºÎ¿¡ ¿µÇâÀ» ¹ÌÄ¡Áö ¾Ê±â ¶§¹®¿¡ ÀǵµÇÏÁö ¾Ê´Â °á°ú¸¦ ¹ß»ý½ÃŰÁö ¾ÊÀ¸¹Ç·Î(ºÎÀÛ¿ëÀÌ ¾ø´Ù) °á°ú¸¦ ¿¹ÃøÇÒ ¼ö ÀÖ´Â ÇÔ¼ö(¼ø¼ö ÇÔ¼ö)°¡ µÈ´Ù. ÀÌ·¯ÇÑ ¿¹Ãø °¡´ÉÇÑ ÇÔ¼ö´Â ¼öÇÐÀûÀ¸·Î ´Ù¸¦ ¼ö ÀÖ¾î Á» ´õ ¸¹Àº ÃÖÀûÈ­ ±âȸ¿Í Á¤È®¼ºÀ» ¾ò´Â´Ù.



Ãâó : ¸¶ÀÌÅ©·Î¼ÒÇÁÆ®¿þ¾î 9¿ùÈ£

Á¦°ø : µ¥ÀÌÅÍ Àü¹®°¡ Áö½ÄÆ÷ÅÐ DBguide.net