<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>산타는 없다</title>
    <link>https://ledpear.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Fri, 10 Apr 2026 21:34:09 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>LEDPEAR</managingEditor>
    <image>
      <title>산타는 없다</title>
      <url>https://tistory1.daumcdn.net/tistory/4419275/attach/5fa81f59466840cda3a5f714227d0726</url>
      <link>https://ledpear.tistory.com</link>
    </image>
    <item>
      <title>[프로그래머스 / programmers] 피로도 - C++</title>
      <link>https://ledpear.tistory.com/126</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 원문&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 설명&lt;/p&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;XX게임에는 피로도 시스템(0 이상의 정수로 표현합니다)이 있으며, 일정 피로도를 사용해서 던전을 탐험할 수 있습니다. 이때, 각 던전마다 탐험을 시작하기 위해 필요한 &quot;최소 필요 피로도&quot;와 던전 탐험을 마쳤을 때 소모되는 &quot;소모 피로도&quot;가 있습니다. &quot;최소 필요 피로도&quot;는 해당 던전을 탐험하기 위해 가지고 있어야 하는 최소한의 피로도를 나타내며, &quot;소모 피로도&quot;는 던전을 탐험한 후 소모되는 피로도를 나타냅니다. 예를 들어 &quot;최소 필요 피로도&quot;가 80, &quot;소모 피로도&quot;가 20인 던전을 탐험하기 위해서는 유저의 현재 남은 피로도는 80 이상 이어야 하며, 던전을 탐험한 후에는 피로도 20이 소모됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 게임에는 하루에 한 번씩 탐험할 수 있는 던전이 여러개 있는데, 한 유저가 오늘 이 던전들을 최대한 많이 탐험하려 합니다. 유저의 현재 피로도 k와 각 던전별 &quot;최소 필요 피로도&quot;, &quot;소모 피로도&quot;가 담긴 2차원 배열 dungeons 가 매개변수로 주어질 때, 유저가 탐험할수 있는 최대 던전 수를 return 하도록 solution 함수를 완성해주세요.&lt;/p&gt;
제한사항
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;k는 1 이상 5,000 이하인 자연수입니다.&lt;/li&gt;
&lt;li&gt;dungeons의 세로(행) 길이(즉, 던전의 개수)는 1 이상 8 이하입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;dungeons의 가로(열) 길이는 2 입니다.&lt;/li&gt;
&lt;li&gt;dungeons의 각 행은 각 던전의 [&quot;최소 필요 피로도&quot;, &quot;소모 피로도&quot;] 입니다.&lt;/li&gt;
&lt;li&gt;&quot;최소 필요 피로도&quot;는 항상 &quot;소모 피로도&quot;보다 크거나 같습니다.&lt;/li&gt;
&lt;li&gt;&quot;최소 필요 피로도&quot;와 &quot;소모 피로도&quot;는 1 이상 1,000 이하인 자연수입니다.&lt;/li&gt;
&lt;li&gt;서로 다른 던전의 [&quot;최소 필요 피로도&quot;, &quot;소모 피로도&quot;]가 서로 같을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
입출력 예kdungeonsresult
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;[[80,20],[50,40],[30,10]]&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
입출력 예 설명
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 피로도는 80입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 첫 번째 &amp;rarr; 두 번째 &amp;rarr; 세 번째 던전 순서로 탐험한다면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 피로도는 80이며, 첫 번째 던전을 돌기위해 필요한 &quot;최소 필요 피로도&quot; 또한 80이므로, 첫 번째 던전을 탐험할 수 있습니다. 첫 번째 던전의 &quot;소모 피로도&quot;는 20이므로, 던전을 탐험한 후 남은 피로도는 60입니다.&lt;/li&gt;
&lt;li&gt;남은 피로도는 60이며, 두 번째 던전을 돌기위해 필요한 &quot;최소 필요 피로도&quot;는 50이므로, 두 번째 던전을 탐험할 수 있습니다. 두 번째 던전의 &quot;소모 피로도&quot;는 40이므로, 던전을 탐험한 후 남은 피로도는 20입니다.&lt;/li&gt;
&lt;li&gt;남은 피로도는 20이며, 세 번째 던전을 돌기위해 필요한 &quot;최소 필요 피로도&quot;는 30입니다. 따라서 세 번째 던전은 탐험할 수 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 첫 번째 &amp;rarr; 세 번째 &amp;rarr; 두 번째 던전 순서로 탐험한다면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 피로도는 80이며, 첫 번째 던전을 돌기위해 필요한 &quot;최소 필요 피로도&quot; 또한 80이므로, 첫 번째 던전을 탐험할 수 있습니다. 첫 번째 던전의 &quot;소모 피로도&quot;는 20이므로, 던전을 탐험한 후 남은 피로도는 60입니다.&lt;/li&gt;
&lt;li&gt;남은 피로도는 60이며, 세 번째 던전을 돌기위해 필요한 &quot;최소 필요 피로도&quot;는 30이므로, 세 번째 던전을 탐험할 수 있습니다. 세 번째 던전의 &quot;소모 피로도&quot;는 10이므로, 던전을 탐험한 후 남은 피로도는 50입니다.&lt;/li&gt;
&lt;li&gt;남은 피로도는 50이며, 두 번째 던전을 돌기위해 필요한 &quot;최소 필요 피로도&quot;는 50이므로, 두 번째 던전을 탐험할 수 있습니다. 두 번째 던전의 &quot;소모 피로도&quot;는 40이므로, 던전을 탐험한 후 남은 피로도는 10입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이 경우 세 던전을 모두 탐험할 수 있으며, 유저가 탐험할 수 있는 최대 던전 수는 3입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;던전의 개수가 최대 8개이므로 재귀 탐색 시 깊이가 최대 8 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 최악 탐색횟수는 8!(팩토리얼) 이므로 40320회가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 재귀함수를 통해 전체 탐색을 하는 백트래킹을 활용하여 문제를 해결하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;풀이과정은 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;함수에 진입했을 때 횟수가 최대 횟수보다 많은지 확인하고 갱신&lt;/li&gt;
&lt;li&gt;배열을 전체 순회하면서 조건(현재 피로도가 최소 필요 피로도 이상일 때)에 만족하면 방문처리를 해주고 재귀 함수 진입&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;성공 코드&lt;/h2&gt;
&lt;pre id=&quot;code_1610278526224&quot; class=&quot;c++ arduino&quot; style=&quot;overflow: auto; margin: 20px auto 0px; padding: 15px; color: #383a42; background: #f6f7f8; overflow-wrap: break-word; font-family: Menlo, Consolas, Monaco, monospace; font-size: 14px; line-height: 20px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: block; border-radius: 3px; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;string&amp;gt;
#include &amp;lt;vector&amp;gt;

using namespace std;

void backTracking(int&amp;amp; fatigue, int&amp;amp; count, int&amp;amp; max, vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; dungeons, vector&amp;lt;bool&amp;gt;&amp;amp; visitCheck)
{
    if (max &amp;lt; count)
    {
        max = count;
    }

    int size = dungeons.size();
    for (int idx = 0; idx &amp;lt; size; ++idx)
    {
        int minimumEntryFatigue = dungeons[idx][0];
        int exhaustionFatigue = dungeons[idx][1];

        //예외처리
        if (true == visitCheck[idx])
        {
            continue;
        }
        if (fatigue &amp;lt; minimumEntryFatigue)
        {
            continue;
        }

        fatigue -= exhaustionFatigue;
        visitCheck[idx] = true;
        ++count;

        backTracking(fatigue, count, max, dungeons, visitCheck);

        fatigue += exhaustionFatigue;
        visitCheck[idx] = false;
        --count;
    }
}

int solution(int k, vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt; dungeons) {
    int answer = -1;
    int count = 0;
    vector&amp;lt;bool&amp;gt; visitCheck(dungeons.size(), false);
    backTracking(k, count, answer, dungeons, visitCheck);

    return answer;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;378&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n942l/btrnDodovQz/KyS7MfJcYxD6NGxgK02kvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n942l/btrnDodovQz/KyS7MfJcYxD6NGxgK02kvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n942l/btrnDodovQz/KyS7MfJcYxD6NGxgK02kvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn942l%2FbtrnDodovQz%2FKyS7MfJcYxD6NGxgK02kvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;378&quot; height=&quot;478&quot; data-origin-width=&quot;378&quot; data-origin-height=&quot;478&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github - &lt;a href=&quot;https://github.com/ledpear&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;github.com/ledpear&lt;/a&gt;&lt;/p&gt;</description>
      <category>코딩테스트/프로그래머스</category>
      <author>LEDPEAR</author>
      <guid isPermaLink="true">https://ledpear.tistory.com/126</guid>
      <comments>https://ledpear.tistory.com/126#entry126comment</comments>
      <pubDate>Sun, 12 Dec 2021 17:47:58 +0900</pubDate>
    </item>
    <item>
      <title>Window 10 Git &amp;amp; Github desktop 설치 및 개발환경 구축</title>
      <link>https://ledpear.tistory.com/125</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;git 그리고 github desktop을 설치하여 개발환경을 구축해보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;git 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://git-scm.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://git-scm.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1637558796871&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Git&quot; data-og-description=&quot;&quot; data-og-host=&quot;git-scm.com&quot; data-og-source-url=&quot;https://git-scm.com/&quot; data-og-url=&quot;https://git-scm.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/xaajN/hyMqTGQrkt/058Peju5WftkgQxATCDnfk/img.png?width=778&amp;amp;height=502&amp;amp;face=0_0_778_502&quot;&gt;&lt;a href=&quot;https://git-scm.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://git-scm.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/xaajN/hyMqTGQrkt/058Peju5WftkgQxATCDnfk/img.png?width=778&amp;amp;height=502&amp;amp;face=0_0_778_502');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Git&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;git-scm.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 링크에 들어가서 최신 파일을 다운받습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;990&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcjDi8/btrlNcsDEVZ/nM6MvRiDyZptWYa6lUxF30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcjDi8/btrlNcsDEVZ/nM6MvRiDyZptWYa6lUxF30/img.png&quot; data-alt=&quot;Download for Window 클릭&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcjDi8/btrlNcsDEVZ/nM6MvRiDyZptWYa6lUxF30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcjDi8%2FbtrlNcsDEVZ%2FnM6MvRiDyZptWYa6lUxF30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;990&quot; height=&quot;654&quot; data-origin-width=&quot;990&quot; data-origin-height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Download for Window 클릭&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;120&quot; data-origin-height=&quot;149&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n8pYU/btrlPoTEtH6/uqkePHcBWwkcGdeFyVLXmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n8pYU/btrlPoTEtH6/uqkePHcBWwkcGdeFyVLXmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n8pYU/btrlPoTEtH6/uqkePHcBWwkcGdeFyVLXmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn8pYU%2FbtrlPoTEtH6%2FuqkePHcBWwkcGdeFyVLXmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;120&quot; height=&quot;149&quot; data-origin-width=&quot;120&quot; data-origin-height=&quot;149&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 파일이 다운로드가 완료되었다면 실행해 설치를 시작합니다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMR2iN/btrlSjYQRXu/fx3z2uhK75oneDpNToxJh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMR2iN/btrlSjYQRXu/fx3z2uhK75oneDpNToxJh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMR2iN/btrlSjYQRXu/fx3z2uhK75oneDpNToxJh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMR2iN%2FbtrlSjYQRXu%2Ffx3z2uhK75oneDpNToxJh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;392&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOwMVk/btrlSiMoMSR/ya4zsk94MsKtwKKRk60qw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOwMVk/btrlSiMoMSR/ya4zsk94MsKtwKKRk60qw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOwMVk/btrlSiMoMSR/ya4zsk94MsKtwKKRk60qw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOwMVk%2FbtrlSiMoMSR%2Fya4zsk94MsKtwKKRk60qw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;392&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NUb9y/btrlQ0rHHSr/pDg14lIsOIKU6BTjGRosg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NUb9y/btrlQ0rHHSr/pDg14lIsOIKU6BTjGRosg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NUb9y/btrlQ0rHHSr/pDg14lIsOIKU6BTjGRosg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNUb9y%2FbtrlQ0rHHSr%2FpDg14lIsOIKU6BTjGRosg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;392&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 githut desktop을 사용할거라 Git GUI는 설치하지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 설정에 자세한 내용은 &lt;a href=&quot;https://goddaehee.tistory.com/216&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이쪽 게시물&lt;/a&gt;을 확인해 주세요 갓대희님깨서 잘 정리해 주셨습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwqItD/btrlOWQE3KH/2DXdXkuuQvpN8svkXIXF80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwqItD/btrlOWQE3KH/2DXdXkuuQvpN8svkXIXF80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwqItD/btrlOWQE3KH/2DXdXkuuQvpN8svkXIXF80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwqItD%2FbtrlOWQE3KH%2F2DXdXkuuQvpN8svkXIXF80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;392&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Z2Gda/btrlJN70VZQ/hJifckIoIp3w7GWldNzKkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Z2Gda/btrlJN70VZQ/hJifckIoIp3w7GWldNzKkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Z2Gda/btrlJN70VZQ/hJifckIoIp3w7GWldNzKkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZ2Gda%2FbtrlJN70VZQ%2FhJifckIoIp3w7GWldNzKkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;392&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qqyAk/btrlNdd3ypI/djhkCYo0XSif9kkyXycQR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qqyAk/btrlNdd3ypI/djhkCYo0XSif9kkyXycQR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qqyAk/btrlNdd3ypI/djhkCYo0XSif9kkyXycQR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqqyAk%2FbtrlNdd3ypI%2FdjhkCYo0XSif9kkyXycQR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;392&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b26XT6/btrlTkwcG1L/PPrnZqRz7CQacAc7f1aKAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b26XT6/btrlTkwcG1L/PPrnZqRz7CQacAc7f1aKAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b26XT6/btrlTkwcG1L/PPrnZqRz7CQacAc7f1aKAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb26XT6%2FbtrlTkwcG1L%2FPPrnZqRz7CQacAc7f1aKAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;392&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWqVhv/btrlSxv2tGa/u0KS7JFevLWougoa3adMw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWqVhv/btrlSxv2tGa/u0KS7JFevLWougoa3adMw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWqVhv/btrlSxv2tGa/u0KS7JFevLWougoa3adMw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWqVhv%2FbtrlSxv2tGa%2Fu0KS7JFevLWougoa3adMw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;392&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kwIIZ/btrlNdyldHV/22tkZkQ21s9LMhBXDZUCvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kwIIZ/btrlNdyldHV/22tkZkQ21s9LMhBXDZUCvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kwIIZ/btrlNdyldHV/22tkZkQ21s9LMhBXDZUCvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkwIIZ%2FbtrlNdyldHV%2F22tkZkQ21s9LMhBXDZUCvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;392&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cils82/btrlNclXwMc/Gn3k3U8gaeJ2uHrXCaot5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cils82/btrlNclXwMc/Gn3k3U8gaeJ2uHrXCaot5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cils82/btrlNclXwMc/Gn3k3U8gaeJ2uHrXCaot5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcils82%2FbtrlNclXwMc%2FGn3k3U8gaeJ2uHrXCaot5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;392&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mVxEB/btrlTlu67od/7A6QhX2g4ypqJqdUF0u4IK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mVxEB/btrlTlu67od/7A6QhX2g4ypqJqdUF0u4IK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mVxEB/btrlTlu67od/7A6QhX2g4ypqJqdUF0u4IK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmVxEB%2FbtrlTlu67od%2F7A6QhX2g4ypqJqdUF0u4IK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;392&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wxLpK/btrlNc7gtRd/CHMTs7L1K8mGW2rMhkMDK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wxLpK/btrlNc7gtRd/CHMTs7L1K8mGW2rMhkMDK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wxLpK/btrlNc7gtRd/CHMTs7L1K8mGW2rMhkMDK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwxLpK%2FbtrlNc7gtRd%2FCHMTs7L1K8mGW2rMhkMDK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;392&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwVagQ/btrlS1cAhJX/itZ86rfGehFfu1I5rPxTD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwVagQ/btrlS1cAhJX/itZ86rfGehFfu1I5rPxTD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwVagQ/btrlS1cAhJX/itZ86rfGehFfu1I5rPxTD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwVagQ%2FbtrlS1cAhJX%2FitZ86rfGehFfu1I5rPxTD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;392&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Enl6w/btrlJM2oDcg/WE8TZIVIG0qNaWq34UeOtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Enl6w/btrlJM2oDcg/WE8TZIVIG0qNaWq34UeOtk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Enl6w/btrlJM2oDcg/WE8TZIVIG0qNaWq34UeOtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEnl6w%2FbtrlJM2oDcg%2FWE8TZIVIG0qNaWq34UeOtk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;392&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMuYzv/btrlNdZqonF/LdPSm8P5t9HgA24RlCmLy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMuYzv/btrlNdZqonF/LdPSm8P5t9HgA24RlCmLy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMuYzv/btrlNdZqonF/LdPSm8P5t9HgA24RlCmLy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMuYzv%2FbtrlNdZqonF%2FLdPSm8P5t9HgA24RlCmLy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;392&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dO79cX/btrlS1cAnXb/XFWSWlwsmyCurumlVu4kQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dO79cX/btrlS1cAnXb/XFWSWlwsmyCurumlVu4kQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dO79cX/btrlS1cAnXb/XFWSWlwsmyCurumlVu4kQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdO79cX%2FbtrlS1cAnXb%2FXFWSWlwsmyCurumlVu4kQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;392&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1&gt;GitHub Desktop 설치&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://desktop.github.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://desktop.github.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1637559559174&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;GitHub Desktop&quot; data-og-description=&quot;Simple collaboration from your desktop&quot; data-og-host=&quot;desktop.github.com&quot; data-og-source-url=&quot;https://desktop.github.com/&quot; data-og-url=&quot;https://desktop.github.com/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://desktop.github.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://desktop.github.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub Desktop&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Simple collaboration from your desktop&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;desktop.github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 링크에서 설치파일을 다운받습니다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;985&quot; data-origin-height=&quot;540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/co8QeX/btrlTkpsAic/p2n6Q6qWBjzCQ6GA1y8KKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/co8QeX/btrlTkpsAic/p2n6Q6qWBjzCQ6GA1y8KKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/co8QeX/btrlTkpsAic/p2n6Q6qWBjzCQ6GA1y8KKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fco8QeX%2FbtrlTkpsAic%2Fp2n6Q6qWBjzCQ6GA1y8KKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;985&quot; height=&quot;540&quot; data-origin-width=&quot;985&quot; data-origin-height=&quot;540&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;136&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZWMon/btrlSvSAc8v/el8hhTAjI6dmarTCCSEckk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZWMon/btrlSvSAc8v/el8hhTAjI6dmarTCCSEckk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZWMon/btrlSvSAc8v/el8hhTAjI6dmarTCCSEckk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZWMon%2FbtrlSvSAc8v%2Fel8hhTAjI6dmarTCCSEckk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;136&quot; height=&quot;156&quot; data-origin-width=&quot;136&quot; data-origin-height=&quot;156&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kTo0N/btrlOWiPQci/EFWYSIjrpfQ7t8sHhgzyS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kTo0N/btrlOWiPQci/EFWYSIjrpfQ7t8sHhgzyS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kTo0N/btrlOWiPQci/EFWYSIjrpfQ7t8sHhgzyS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkTo0N%2FbtrlOWiPQci%2FEFWYSIjrpfQ7t8sHhgzyS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;960&quot; height=&quot;660&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJ4Vyp/btrlOWC9l5p/mUYnL04uqZbKBqq9x7qb61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJ4Vyp/btrlOWC9l5p/mUYnL04uqZbKBqq9x7qb61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJ4Vyp/btrlOWC9l5p/mUYnL04uqZbKBqq9x7qb61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJ4Vyp%2FbtrlOWC9l5p%2FmUYnL04uqZbKBqq9x7qb61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;960&quot; height=&quot;660&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 화면에서 사용할 계정을 연결하면 되는데 이때 자신의 계정 정보를 그대로 사용할지 아니면 다르게 사용할지 설정할 수 있습니다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLJWK9/btrlTkQxHUj/kNhkOpCCqXfhzpwHYWlfx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLJWK9/btrlTkQxHUj/kNhkOpCCqXfhzpwHYWlfx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLJWK9/btrlTkQxHUj/kNhkOpCCqXfhzpwHYWlfx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLJWK9%2FbtrlTkQxHUj%2FkNhkOpCCqXfhzpwHYWlfx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;960&quot; height=&quot;660&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 연결을 완료했습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 저는 Add local repository를 눌러 기존에 있던 repository 폴더에 연결하였습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로 repository폴더를 만들고 파일을 다운받으려면 Clone repository를 누르면 됩니다.&lt;/p&gt;</description>
      <category>개발환경</category>
      <category>git</category>
      <category>Github</category>
      <category>GitHub Desktop</category>
      <author>LEDPEAR</author>
      <guid isPermaLink="true">https://ledpear.tistory.com/125</guid>
      <comments>https://ledpear.tistory.com/125#entry125comment</comments>
      <pubDate>Mon, 22 Nov 2021 14:49:44 +0900</pubDate>
    </item>
    <item>
      <title>Window via C/C++ 9장 - 커널 오브젝트를 이용한 스레드 동기화</title>
      <link>https://ledpear.tistory.com/124</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0. 개요
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유저 모드 동기화의 최대 장점은 빠르다는 것이다. 스레드의 수행 성능이 중요한 경우라면 항상 유저 모드 스레드 동기화 메커니즘을 가장 먼저 고려해 보아야 한다.&lt;/li&gt;
&lt;li&gt;유저 모드에서 커널 모드로의 전환은 약 200CPU 사이클 정도가 필요한 비싼 작업이다.&lt;/li&gt;
&lt;li&gt;프로세스 커널 오브젝트의 경우 관련된 프로세스가 종료되면 운영체제가 자동적으로 해당 오브젝트를 시그널 상태로 변경한다. 프로세스 커널 오브젝트의 경우 한 번 시그널 상태가 되면 다시 논시그널 상태로 변경될 수 없으며 영원히 시그널 상태로 남게 된다.&lt;/li&gt;
&lt;li&gt;프로세스 커널 오브젝트의 내부에는 오브젝트 생성 시 FALSE(논시그널)로 초기화되는 BOOL 값이 있는데, 이 값은 프로세스가 종료되면 운영체제에 의해 자동적으로 TURE로 변경되어 해당 커널 오브젝트가 시그널 상태임을 나타내게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;1. 대기 함수들
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대기 함수를 호출하면 인자로 전달한 커널 오브젝트가 시그널 상태가 될 때까지 이 함수를 호출한 스레드를 대기 상태로 유지한다. 만일 대기 함수가 호출된 시점에 커널 오브젝트가 이미 시그널 상태였다면 스레드는 대기 상태로 전환되지 않는다.&lt;/li&gt;
&lt;li&gt;대기 함수 중 가장 많이 쓰이는 함수는&lt;br /&gt;- DWORD WaitForSingleObject(HANDLE hObject, DWORD dwMilliseconds);&lt;br /&gt;다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;hObject : 시그널과 논시그널 상태가 될 수 있는 커널 오브젝트의 핸들&lt;/li&gt;
&lt;li&gt;dwMilliseconds :&amp;nbsp;커널 오브젝트가 시그널 상태가 될 때까지 얼마나 오랫동안 기다려 볼 것인지를 나타내는 시간 값&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DWORD WaitForMultipleObjects(DWORD dwCoutn, CONST HANDLE* phObjects, BOOL bWaitAll,&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;DWORD dwMilliseconds);
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나가 아닌 여러 개의 커널 오브젝트들에 대해 시그널 상태를 동시에 검사할 수 있다.&lt;/li&gt;
&lt;li&gt;dwCount : 검사해야 하는 커널 오브젝트의 개수&lt;/li&gt;
&lt;li&gt;phObjects : 커널 오브젝트 핸들의 배열을 가리키는 포인터&lt;/li&gt;
&lt;li&gt;bWaitAll : 모든 오브젝트가 시그널 상태가 될 때가 대기 할 것인지(TRUE), 하나라도 시그널 상태가 되면 빠져나올 것인지(FALSE)&lt;/li&gt;
&lt;li&gt;dwMilliseconds : 얼마나 오래 기다릴 것인지 나타내는 시간값&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2. 성공적인 대기의 부가적인 영향
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성공적인 호출 : 매개변수로 전달한 커널 오브젝트가 시그널 상태가 되어 WAIT_OBJECT_0을 반환하는 경우&lt;/li&gt;
&lt;li&gt;성공적이지 않은 호출 : WAIT_TIMEOUT이나 WAIT_FAILED를 반환하는 경우를 말하며 오브젝트의 상태가 변경되지 않는다.&lt;/li&gt;
&lt;li&gt;① 자동 리셋 이벤트 커널 오브젝트 핸들을 매개변수로 대기 함수를 호출하는 경우, 이 오브젝트가 시그널 상태가 되면 WAIT_OBJECT_0을 반환한다&lt;/li&gt;
&lt;li&gt;② 함수가 반환되기 직전에 이벤트 커널 오브젝트가 논시그널 상태로 변경된다&lt;/li&gt;
&lt;li&gt;마이크로소프트가 사용하고 있는 스레드 순서 알고리즘은 '선입선출' 방식이지만 이러한 동작은 예고 없이 변경될 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;3. 이벤트 커널 오브젝트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 커널 오브젝트 중 이벤트가 가장 단순한 구조를 가지고 있다. 이벤트는 사용 카운트(모든 커널 오브젝트가 가지고 있는), 자동 리셋 이벤트인지 수동 리셋 이벤트인지 판별하는 BOOL 값, 이벤트가 시그널 상태인지 논시그널 상태인지를 나타내는 BOOL값으로 이루어져 있다&lt;/li&gt;
&lt;li&gt;이벤트는 어떤 작업이 완료되었음을 알리기 위해 주로 사용되며, 수동 리셋 이벤트가 시그널 상태가 되면 이 이벤트를 기다리고 있던 모든 스레드들은 동시에 스케줄 가능 상태가 된다. 자동 리셋 이벤트의 경우에는 대기 중인 스레드들 중 하나의 스레드만이 스케줄 가능 상태가 된다.&lt;/li&gt;
&lt;li&gt;이벤트는 하나의 스레드가 초기 작업을수행하고 이후 다른 스레드에게 나머지 작업을 수행할 것을 알려주기 위해 사용하는 경우가 많다.&lt;/li&gt;
&lt;li&gt;마이크로소프트는 두 가지 형태의 이벤트 중 자동 리셋 이벤트에 대해서만 성공적인 대기의 부가적인 영향을 정의하고 있다. 만일 자동 리셋 이벤트에 대해 성공적인 대기가 이루어지면 자동적으로 이벤트의 상태는 논시그널로 바뀐다.&lt;/li&gt;
&lt;li&gt;BOOL PulseEvent(HANDLE hEvent) : 이벤트를 시그널 상태로 변경하였다가 곧바로 다시 논시그널 상태로 변경한다. 수동 리셋 이벤트를 인자로 호출하면 이 이벤트가 시그널 상태가 되기를 기다리던 모든 스레드가 한꺼번에 스케줄 가능 상태가 되며, 자동 리셋 이벤트를 인자로 호출하면 그 중 하나의 스레드만이 스케줄 가능 상태가 된다.&lt;/li&gt;
&lt;li&gt;① 핸드셰이크 예제 애플리케이션&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;4. 대기 타이머 커널 오브젝트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대기 타이머(waitable timer) : 특정 시간에 혹은 일정한 간격을 두고 자신을 시그널 상태로 만드는 커널 오브젝트로서, 주로 특정 시간에 맞추어 어떤 작업을 수행해야 할 경우에 사용&lt;br /&gt;HANDLE CreateWaitableTimer(PSECURITY_ATTRIBUTES psa, BOOL bManualReset, PCTSTR pszName);&lt;/li&gt;
&lt;li&gt;다른 프로세스에서는 OpenWaitableTimer 함수를 이용하여 이미 생성된 대기 타이머를 가리키는 프로세스 고유의 핸들 값을 얻을 수 있다.&lt;br /&gt;HANDLE OpneWaitableTimer(DWORD dwDesiredAccess, BOOL bManualReset, PCTSTR pszName);&lt;/li&gt;
&lt;li&gt;bManualReset : 수동 리셋 타이머를 생성할 것인지 아니면 자동 리셋 타이머인지를 생성할 것인지를 결정하는 값을 전달한다. 자동 라셋 타이머가 시그널 상태가 되면 이 타이머를 대기 중인 스레드들 중 유일하게 한 개의 스레드만이 스케줄 가능 상태가 된다.&lt;/li&gt;
&lt;li&gt;대기 타이머는 항상 논시그널 상태로 생성되며, 언제 시그널 상태가 될것인지 지정하기 위해 SetWaitableTimer 함수를 사용한다&lt;br /&gt;BOOL SetWaitableTimer(HANDLE hTimer, const LARGE_INTEGER *pDueTime, LONG lPeriod,&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; PTIMERAPCROUTINE pfnCompletionRoutine,&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; PVOID pvArgToCompletionRoutine, BOOL bResume);&lt;/li&gt;
&lt;li&gt;① 대기 타이머를 이용하여 APC 요청을 스레드의 APC 큐에 삽입하는 방법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마이크로소프트는 SetWaitableTimer를 이용하여 타이머가 시그널 상태가 되었을 때 비동기 함수 호출(asynchronous procedure call / APC) 요청을 스레드의 APC 큐에 삽입할 수 있는 방법을 제공하고 있다.&lt;/li&gt;
&lt;li&gt;스레드는 단일의 타이머 핸들에 대해 타이머 커널 오브젝트에 대한 시그널 대기와 얼러터블 상태 대기를 동시에 수행해서는 안된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;② 타이머와 관련된 미결 문제
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;타이머는 종종 통신 프로토콜을 구현하는데 사용되기도 한다. 이때, 매 요청별로 타이머 커널 오브젝트를 생성하게 되면 시스템의 성능이 저하될 수도 있기 때문에 가능하다면 하나의 타이머 오브젝트만을 생성하고, 시그널 시간을 적절히 변경해 가면서 재사용하는 것이 좋다.&lt;/li&gt;
&lt;li&gt;직접 타이머 시간을 변경하고 재설정하는 작업을 수행하는 대신 새롭게 추가된 스테드 풀링 함수의 하나인 CreateThreadpoolTimer와 같은 함수를 사용한다.&lt;/li&gt;
&lt;li&gt;윈도우 개발에 익숙한 개발자라면 대기 타이머와 유저타이머(SetTimer 함수를 사용하는)를 비교해 보려 할 것이다. 가장 큰 차이점은 유저 타이머의 경우 비교적 리소스를 많이 사용하는 사용자 인터페이스 환경 하에서만 수행된다는 것이다. 대기 타이머는 커널 오브젝트이기 때문에 다수의 스레드에 의해 공유될 수 있으며, 좀더 보안에 안정적이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;5. 세마포어 커널 오브젝트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세마포어 커널 오브젝트는 리소스의 개수를 고려해야 하는 상황에서 주로 사용된다. 이 커널 오브젝트는 모든 커널 오브젝트와 마찬가지로 사용 카운트를 가지고 있으며, 이 외에도 2개의 32비트 값을 가지고 있어서 최대 리소스 카운트와 현재 리소스 카운트를 저장하고 있다. 최대 리소스 카운트는 세마포어가 제어할 수 있는 리소스의 최대 개수를 나타내는 데 사용되고, 현재 리소스 카운트는 사용 가능한 리소스의 개수를 나타내는 데 사용된다.&lt;/li&gt;
&lt;li&gt;세마포어는 다음의 규칙에 따라 동작한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 리소스 카운트가 0보다 크면 세마포어는 시그널 상태가 된다.&lt;/li&gt;
&lt;li&gt;현재 리소스 카운트가 0이면 세마포어는 논시그널 상태가 도니다.&lt;/li&gt;
&lt;li&gt;시스템은 현재 리소스 카운트를 음수로 만들 수 없다.&lt;/li&gt;
&lt;li&gt;현재 리소스 카운트는 최대 리소스 카운트보다 커질 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;세마포어를 사용할 때에는 오브젝트의 사용 카운트와 현재 리소스 카운트를 혼돈하지 않도록 주의해야 한다.&lt;/li&gt;
&lt;li&gt;세마포어 커널 오브젝트를 생성하려면 CreateSemaphore 함수를 사용하면 된다.&lt;br /&gt;- HANDLE CreateSemaphore(PSECURITY_ATTRIBUTE psa, LONG lInitialCount,&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; LONG lMaximumCount, PCTSTR pszName);&lt;/li&gt;
&lt;li&gt;CreateSemaphoreEx 함수를 이용하면 dwDesiredAccess 매개변수를 통해 세마포어에 대한 접근 권한을 바로 지정할 수 있다. dwFlags 매개변수는 항상 0으로 설정해야 한다..&lt;br /&gt;- HANDLE CreateSemaphoreEx(PSECURITY_ATTRIBUTE psa, LONG lInitialCount,&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;LONG lMaximumCount, PCTSTR pszName, DWORD dwFlags,&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;DWORD dwDesiredAccess);&lt;/li&gt;
&lt;li&gt;모든 프로세스는 OpenSemaphore 함수를 이용하여 이미 생성된 세마포어를 가리키는 프로세스 고유의 핸들 값을 얻을 수 있다.&lt;br /&gt;- HANDLE OpenSemaphore(DWORD dwDesiredAccess, BOOL bInheritHandle, PCTSTR pszName);&lt;/li&gt;
&lt;li&gt;세마포어의 현재 리소스 카운트를 증가시키기 위해서는 ReleaseSemaphore 함수를 호출하면 된다.&lt;br /&gt;- BOOL ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount, PLONG plPreviousCount);&lt;/li&gt;
&lt;li&gt;세마포어의 현재 리소스 카운트 값을 변경하지 않도로 그 값을 알수 있는 방법은 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;6. 뮤텍스 커널 오브젝트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;뮤텍스 커널 오브젝트는 스레드가 단일의 리소스에 대해 배타적으로 접근할 수 있도록 해 준다. 사실 뮤텍스(MUTual EXclusion)이라는 이름도 이러한 특성으로부터 기인한 것이다. 이 커널 오브젝트는 사용 카운트, 스레드ID, 반복 카운터를 저장할 수 있는 공간을 가지고 있다.&lt;/li&gt;
&lt;li&gt;뮤텍스의 동작 방식은 크리티컬 섹션과 동일하다. 하지만 크리티컬 섹션이 유저 모드 동기화 오브젝트인 데 반해 뮤텍스는 커널 오브젝트라는 차이점이 있다. 이러한 차이점 때문에 뮤텍스는 크리티컬 섹션에 비해 느리지만, 서로 다른 프로세스에서 동일 뮤텍스에 대해 접근이 가능하며, 리소스에 대한 접근 권한을 획득할 때 시간 제한을 지정할 수 있다는 장점이 있다.&lt;/li&gt;
&lt;li&gt;스레드 ID : 시스템 내의 어떤 스레드가 뮤텍스를 소유하고 있는지를 나타내는 값이다.&lt;/li&gt;
&lt;li&gt;반복 카운터 : 뮤텍스를 소유하고 있는 스레드가 몇 회나 반복적으로 뮤텍스를 소유하고자 했는지에 대한 횟수를 나타내는 값이다.&lt;/li&gt;
&lt;li&gt;뮤텍스는 다수의 스레드가 동시에 접근하는 메모리 블록을 보호하기 위해 사용되기도 한다.&lt;/li&gt;
&lt;li&gt;뮤텍스는 다음의 규칙에 따라 동작한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드ID가 0(유효하지 않는 스레드ID)이면 뮤텍스는 어떠한 스레드에 의해서도 소유되지 않은 것이며, 이때 뮤텍스는 시그널 상태가 된다.&lt;/li&gt;
&lt;li&gt;스레드ID가 0이 아니면 뮤텍스는 특정 스레드에 의해 소유된 것이며, 이때 논시그널 상태가 된다.&lt;/li&gt;
&lt;li&gt;다른 커널 오브젝트와는 다르게 뮤텍스는 특수한 코드를 포함하고 있어서 일반적인 규칙을 위반하는 경우도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;뮤텍스를 사용하려면 CreateMutex 함수를 호출해서 뮤텍스를 생성해야 한다.&lt;br /&gt;- HANDLE CreateMutex(PSECURITY_ATTRIBUTES psa, BOOL bInitialOwner, PCTSTR pszName);&lt;/li&gt;
&lt;li&gt;CreateMutexEx 함수를 이용하면 dwDesiredAccess 매개변수를 통해 뮤텍스에 대한 접근 권한을 바로 지정할 수 있다. dwFlags 매개변수는 CreateMutex의 bInitialOwner 매개변수와 동일한 용도로 사용된다: 0은 FALSE를 CREATE_MUTEX_INITIAL_OWNEL는 TRUE와 동일한 의미로 사용된다.&lt;br /&gt;- HANDLE CreateMutexEx(PSECURITY_ATTRIBUTES psa, PCTSTR pszName, DWORD dwFlags,&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; DWORD dwDesiredAccess);&lt;/li&gt;
&lt;li&gt;모든 프로세스는 OpenMutex 함수를 이용하여 이미 생성된 뮤텍스를 가리키는 프로세스 고유의 핸들값을 얻을 수 있다.&lt;br /&gt;- HANDLE OpenMutex(DWORD dwDesiredAccess, BOOL bInheritHandle, PCTSTR pszName);&lt;/li&gt;
&lt;li&gt;bInitialOwner 매개변수는 뮤텍스의 초기 상태를 제어하는 용도로 사용된다. 이값을 FALSE(보통의 경우)로 설정하면 뮤텍스의 스레드ID와 반복 카운터는 0으로 설정된다. 이것은 뮤텍스가 어떠한 스레드에 의해서도 소유되지 않았으며, 시그널 상태임을 나타내게 된다. 만일 bInitialOwner 값을 TURE로 설정하게 되면 뮤텍스의 스레드ID는 함수를 호출한스레드의 ID로 설정되며, 반복 카운터는 1로 설정된다. 스레드ID가 0이 아니므로 뮤텍스는 논시그널 상태가 된다.&lt;/li&gt;
&lt;li&gt;리소스에 대한 접근 권한을 획득한 스레드가 더 이상 리소스를 사용할 필요가 없어지면 반드시 ReleaseMutex 함수를 호출하여 뮤텍스의 소유권을 해제해 주어야 한다&lt;br /&gt;- BOOL ReleaseMutex(HANDLE hMutex);&lt;/li&gt;
&lt;li&gt;① 버림 문제 (Abandonment issues)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;뮤텍스는 다른 모든 커널 오브젝트와는 다르게 &quot;스레드 소유권(thread ownership)&quot;의 개념을 가지고 있다. 뮤텍스만이 어떤 스레드가 성공적인 대기를 수행하였는지를 기록해 둔다. 이러한 뮤텍스의 스레드 소유권이라는 개념 때문에 뮤텍스가 논 시그널 상태임에도 불구하고 스레드가 뮤텍스를 다시 소유할 수 있는 예외적인 규칙을 가지게 된 것이다.&lt;/li&gt;
&lt;li&gt;뮤텍스와 스레드 커널 오브젝트를 계속해서 추적하고 있기 때문에 언제 뮤텍스가 벼려졌는지(소유권을 해제하지 않고 스레드 종료) 정확히 알 수 있으며, 뮤텍스의 버림이 발생하면 버려진 뮤텍스의 스레드 ID와 반복 카운트를 0으로 변경한다. 이 경우 대기 함수는 WAIT_ABANDONED라는 특별한 값을 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;② 뮤텍스와 크리티컬 섹션
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 29.0476%;&quot;&gt;특성&lt;/td&gt;
&lt;td style=&quot;width: 37.619%;&quot;&gt;뮤텍스&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;크리티컬 섹션&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 29.0476%;&quot;&gt;성능&lt;/td&gt;
&lt;td style=&quot;width: 37.619%;&quot;&gt;느림&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;빠름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 29.0476%;&quot;&gt;프로세스들 간에&lt;br /&gt;사용 가능 여부&lt;/td&gt;
&lt;td style=&quot;width: 37.619%;&quot;&gt;가능&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;불가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 29.0476%;&quot;&gt;선언&lt;/td&gt;
&lt;td style=&quot;width: 37.619%;&quot;&gt;HANDLE hmtx;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;CRITICAL_SECTION cs;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 29.0476%;&quot;&gt;초기화&lt;/td&gt;
&lt;td style=&quot;width: 37.619%;&quot;&gt;hmtx&lt;br /&gt;= CreateMutex(NULL, FALSE, NULL);&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;InitializeCriticalSection(&amp;amp;cs);&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 29.0476%;&quot;&gt;삭제&lt;/td&gt;
&lt;td style=&quot;width: 37.619%;&quot;&gt;CloseHandle(hmtx);&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;DeleteCriticalSection(&amp;amp;cs);&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 29.0476%;&quot;&gt;무한 대기&lt;/td&gt;
&lt;td style=&quot;width: 37.619%;&quot;&gt;WaitForSingleObject(hmtx, INFINITE);&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;EnterCriticalSection(&amp;amp;cs);&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 29.0476%;&quot;&gt;0 대기&lt;/td&gt;
&lt;td style=&quot;width: 37.619%;&quot;&gt;WaitForSingleObject(hmtx, 0);&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;TryEnterCriticalSection(&amp;amp;cs);&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 29.0476%;&quot;&gt;임의 시간 대기&lt;/td&gt;
&lt;td style=&quot;width: 37.619%;&quot;&gt;WaitForSingleObject(hmtx, dwMilliseconds);&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;불가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 29.0476%;&quot;&gt;해제&lt;/td&gt;
&lt;td style=&quot;width: 37.619%;&quot;&gt;ReleaseMutex(hmtx);&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;LeaveCriticalSection(&amp;amp;cs);&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 29.0476%;&quot;&gt;다른 커널 오브젝트와 함께&lt;br /&gt;개기 가능 여부&lt;/td&gt;
&lt;td style=&quot;width: 37.619%;&quot;&gt;가능 (WaitForMultipleObjects나&lt;br /&gt;유사 함수를 이용)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;불가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;③ 큐 예제 애플리케이션&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;7. 편리한 스레드 동기화 오브젝트 표
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 425px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 50px;&quot;&gt;
&lt;td style=&quot;width: 24.0438%; height: 50px;&quot;&gt;오브젝트&lt;/td&gt;
&lt;td style=&quot;width: 25.9562%; height: 50px;&quot;&gt;논시그널 상태&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 50px;&quot;&gt;시그널 상태&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 50px;&quot;&gt;성공적인 대기의&lt;br /&gt;부가적인 영향&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 24.0438%; height: 25px;&quot;&gt;프로세스&lt;/td&gt;
&lt;td style=&quot;width: 25.9562%; height: 25px;&quot;&gt;프로세스가 수행 중&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;프로세스가 종료됨&lt;br /&gt;(ExitProcess,&lt;br /&gt;TerminateProcess)&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 24.0438%; height: 25px;&quot;&gt;스레드&lt;/td&gt;
&lt;td style=&quot;width: 25.9562%; height: 25px;&quot;&gt;스레드가 수행 중&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;스레드가 종료됨&lt;br /&gt;(ExitThread,&lt;br /&gt;TerminateThread)&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 24.0438%; height: 25px;&quot;&gt;잡&lt;/td&gt;
&lt;td style=&quot;width: 25.9562%; height: 25px;&quot;&gt;잡 타임을 초과하지 않음&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;잡 타임 초과&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 24.0438%; height: 25px;&quot;&gt;파일&lt;/td&gt;
&lt;td style=&quot;width: 25.9562%; height: 25px;&quot;&gt;I/O 요청이 수행 중&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;I/O 요청이 완료됨&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 24.0438%; height: 25px;&quot;&gt;콘솔 입력&lt;/td&gt;
&lt;td style=&quot;width: 25.9562%; height: 25px;&quot;&gt;입력이 없음&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;입력이 있음&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 24.0438%; height: 25px;&quot;&gt;파일 변경 통지&lt;/td&gt;
&lt;td style=&quot;width: 25.9562%; height: 25px;&quot;&gt;파일이 변경되지 않음&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;파일시스템이 파일의 변경사항이 있음을 확인&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;통지를 리셋&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 24.0438%; height: 25px;&quot;&gt;자동 리셋 이벤트&lt;/td&gt;
&lt;td style=&quot;width: 25.9562%; height: 25px;&quot;&gt;ResetEvent, PulseEvent, 또는 성공적인 대기&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;SetEvent/PulseEvent가 호출됨&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;이벤트 리셋&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 24.0438%; height: 25px;&quot;&gt;수동 리셋 이벤트&lt;/td&gt;
&lt;td style=&quot;width: 25.9562%; height: 25px;&quot;&gt;ResetEvent, PulseEvent&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;SetEvent/PulseEvent가 호출됨&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 24.0438%; height: 25px;&quot;&gt;자동 리셋 대기 타이머&lt;/td&gt;
&lt;td style=&quot;width: 25.9562%; height: 25px;&quot;&gt;CancelWaitableTimer&lt;br /&gt;또는 성공적인 대기&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;설정한 시간에 도달함&lt;br /&gt;(setWaitableTimer)&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;타이머 리셋&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 24.0438%; height: 25px;&quot;&gt;수동 리셋 대기 타이머&lt;/td&gt;
&lt;td style=&quot;width: 25.9562%; height: 25px;&quot;&gt;CancelWaitableTimer&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;설정한 시간에 도달함&lt;br /&gt;(setWaitableTimer)&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 24.0438%; height: 25px;&quot;&gt;세마포어&lt;/td&gt;
&lt;td style=&quot;width: 25.9562%; height: 25px;&quot;&gt;성공적인 대기&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;카운트가 0보다 큼&lt;br /&gt;(ReleaseSemaphore)&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;카운트 1 감소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 24.0438%; height: 25px;&quot;&gt;뮤텍스&lt;/td&gt;
&lt;td style=&quot;width: 25.9562%; height: 25px;&quot;&gt;성공적인 대기&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;스레드에 의해 소유되지 않음(ReleaseMutex)&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;스레드가 소유권을 가짐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 24.0438%; height: 25px;&quot;&gt;크리티컬 섹션&lt;br /&gt;(유저모드)&lt;/td&gt;
&lt;td style=&quot;width: 25.9562%; height: 25px;&quot;&gt;성공적인 대기&lt;br /&gt;((Try)EnterCriticalSection)&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;스레드에 의해 소유되지 않음&lt;br /&gt;(LeaveCriticalSection)&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;스레드가 소유권을 가짐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 24.0438%; height: 25px;&quot;&gt;SRWLock&lt;br /&gt;(유저모드)&lt;/td&gt;
&lt;td style=&quot;width: 25.9562%; height: 25px;&quot;&gt;성공적인 대기&lt;br /&gt;(AcquireSRWLock&lt;br /&gt;(Exclusive))&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;스레드에 의해 소유되지 않음&lt;br /&gt;(ReleaseSRWLock&amp;nbsp;&lt;br /&gt;(Exclustive))&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;스레드가 소유권을 가짐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 24.0438%; height: 25px;&quot;&gt;조건변수(유저모드)&lt;/td&gt;
&lt;td style=&quot;width: 25.9562%; height: 25px;&quot;&gt;성공적인 대기&lt;br /&gt;(SleepConditionVariable)&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;깨어남 (Wake (All)&lt;br /&gt;condition Variable)&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;8. 그 외의 스레드 동기화 함수들
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;① 비동기 장치 I/O
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비동기 장치 I/O란 스레드가 읽기/쓰기 동작을 수행할 때 요청한 동작을 완료할 때까지 대기하지 않고 다른 작업을 수행할 수 있게 해 주는 방식이다.&lt;/li&gt;
&lt;li&gt;예를 들어 아주 큰 파일을 메모리로 읽어 와야 하는 경우 시스템에게 해당 파일을 메모리로 읽어 올 것을 명령하고 시스템이 파일을 읽는 동안 다른 작업을 수행할 수 있도록 해 주는 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;② WaitForInputIdle
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드는 WaitForInputIdle 함수를 호출하여 대기 상태로 진입할 수 있다.&lt;br /&gt;- DWORD WaitForInputIdle(HANDLE hProcess, DWORD dwMilliseconds)&lt;/li&gt;
&lt;li&gt;이 함수를 호출하면 hProcess가 가리키는 프로세스의 첫 번째 윈도우를 생성한 스레드가 대기 상태가 될 때까지 WaitForInputIdle 함수를 호출한 스레드를 대기 상태로 유지한다.&lt;/li&gt;
&lt;li&gt;이 함수는 페어런트 프로세스가 CreateProcess를 호출하여 차일드 프로세스의 생성을 요청한 후 차일드 프로세스가 완전히 초기화될 때까지 대기하도록 하고 싶은 경우에 유용하게 사용될 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;③ MsgWaitForMulipleObjects(Ex)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MsgWaitForMulipleObjects나 MsgWaitForMulipleObjectsEx 함수를 사용하여 스레드가 메시지를 대기하도록 할 수 있다&lt;/li&gt;
&lt;li&gt;이 함수는 WaitForMulipleObjects 함수와 매우 유사하다. 차이점이라면 이 함수들은 커널 오브젝트가 시그널될 때 외에도 이 함수를 호출한 스레드가 생성한 윈도우에 메시지가 전달되었을 경우에도 스케줄 가능 상태가 된다는 것이다.&lt;/li&gt;
&lt;li&gt;윈도우를 생성하고 사용자 인터페이스와 관련된 작업을 수행하는 스레드라면 사용자 인터페이스가 응답하지 않는 상황을 피해 WaitForMulipleObjects 대신 이 함수를 사용하는 것이 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;④ WaitForDebugEvent
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;디버거(debugger)가 수행되고 디버기(debugee)가 연결되면 디버거는 운영체제가 디버기와 관련된 디버그 이벤트를 전달해 줄 때까지 유휴 상태로 대기하게 된다. 디버거가 디버그 이벤트를 기다리기 위해서는 이 함수를 호출하면 된다.&lt;/li&gt;
&lt;li&gt;디버거 이 함수를 호출하면 디버거의 스레드는 대기 상태가 된다. 시스템은 디버그 이벤트가 발생한 경우 WaitForDebugEvent가 반환되도록 하여 디버그 이벤트가 발생하였음을 알려준다. pde 매개변수가 가리키는 구조체는 디버거의 스레드가 깨어나기 전에 시스템에 의해 채워지며, 어떤 디버그 이벤트가 발생했는지에 대한 정보를 포함하고 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;⑤ SignalObjectAndWait
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 커널 오브젝트를 시그널 상태로 만들어주고, 이와는 또 따른 커널 오브젝트가 시그널 상태가 되기를 대기하는 기능을 원자적으로 수행한다.&lt;/li&gt;
&lt;li&gt;DWORD SignalObjectAndWait(&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; HANDLE hObjectToSignal,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; HANDLE hObjectToWaitOn,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; DWORD dwMilliseconds,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; BOOL bAlertable);&lt;/li&gt;
&lt;li&gt;이 함수를 호출할 때에는 hObjectToSignal 매개변수로 뮤텍스, 세마포어, 또는 이벤트가 전달되어야 하며, 다른 형태의 오브젝트 핸들이 전달되면 WAIT_FAILED가 반환되며, 이때 GetLastError를 호출하면 EEROR_INVALID_HANDLE이 반환된다. 이 함수는 전달되는 핸들의 오브젝트 타입에 맞추어 내부적으로 ReleaseMutex, ReleaseSemaphore(1을 인자로), 또는 SetEvent 함수를 각각 호출해 준다.&lt;/li&gt;
&lt;li&gt;hObjectToWaitOn 매개변수로는 뮤텍스, 세마포어, 이벤트, 타이머, 프로세스, 스레드, 잡, 콘솔 입력, 그리고 변경 통지에 대한 핸들을 전달할 수 있다.&lt;/li&gt;
&lt;li&gt;dwMilliseconds : 얼마만큼 대기할 것인지 지정&lt;/li&gt;
&lt;li&gt;bAlertable : 스레드가 대기 상태인 동안 비동기 프로세저 호출을 수행할 수 있도록 할지의 여부&lt;/li&gt;
&lt;li&gt;이 함수는 두 가지 이유로 인해 윈도우에 추가되었다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;첫째, 개발자들은 특정 오브젝트를 시그널 상태로 만들어준 후 다른 오브젝트를 대기하는 식의 코드를 자주 작성하게 되는데, 단일 함수로 이와 같은 작업을 수행하게 되면 수행 시간을 절약해 주는 효과가 있다.&lt;/li&gt;
&lt;li&gt;둘째, SignalObjectAndWait 함수를 이용하면 이 함수를 호출한 스레드가 대기 상태에 있음을 보증할 수 있기 때문에 앞서 설명한 PulseEvent와 같은 함수를 사용할 때 유용하게 활용될 수 있다. PulseEvent는 특정 이벤트 시그널 상태로 변경하였다가 그 즉시 논시그널 상태로 변경하게 되는데, 어떠한 이벤트를 대기 중인 스레드가 없다면 이벤트가 시그널되었는지를 감지할 수가 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;⑥ 대기 목록 순회 API를 이용한 데드락 감지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>프로그래밍 서적/Window via C++</category>
      <author>LEDPEAR</author>
      <guid isPermaLink="true">https://ledpear.tistory.com/124</guid>
      <comments>https://ledpear.tistory.com/124#entry124comment</comments>
      <pubDate>Sun, 7 Nov 2021 18:03:11 +0900</pubDate>
    </item>
    <item>
      <title>Window via C/C++ 8장 - 유저 모드에서의 스레드 동기화</title>
      <link>https://ledpear.tistory.com/123</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0. 개요
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마이크로 소프트 윈도우는 모든 스레드가 상호 통신 없이 각자의 작업을 수행할 때 최고의 성능을 발휘 한다. 다시 말해 동기화를 수행하지 않을 때를 의미한다.&lt;/li&gt;
&lt;li&gt;모든 스레드들은 힙(heap), 시리얼 포트, 파일, 윈도우와 같이 셀 수 없이 많은 종류의 시스템 리소스에 접근하게 된다.&lt;/li&gt;
&lt;li&gt;다음 두 가지 기본적인 상황에서 스레드는 상호 통신을 수행해야 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다수의 스레드가 공유 리소스에 접근해야 하며, 리소스가 손상되지 않도록 해야하는 경우&lt;/li&gt;
&lt;li&gt;어떤 스레드가 하나 혹은 다수의 다른 스레드에게 작업이 완료되었음을 알려야 하는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;1. 원자적 접근 : Interlocked 함수들
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터락 계열의 함수들은 모두 원자적으로 값을 다룬다.&lt;/li&gt;
&lt;li&gt;LONG InterlockedExchangeAdd ( PLONG volatile plAdded, LONG lIncrement);&lt;/li&gt;
&lt;li&gt;LONGLONG InterlockedExchangeAdd64 ( PLONGLONG volatile pllAdded, LONGLONG llIncrement);&lt;/li&gt;
&lt;li&gt;이 함수를 사용할 때에는 long 값을 저장하고 있는 변수의 주소와 얼마만큼 증가시킬 것인가를 나타내는 값을 인자로 전달하기만 하면 된다.&lt;/li&gt;
&lt;li&gt;예) InterlockedExchangeAdd(&amp;amp;g_x, 1); // g_x을 동기화하여 1 증가&lt;/li&gt;
&lt;li&gt;공유되는 변수 값을 수정하려고 시도하는 모든 스레드들은 반드시 이 함수를 사용해야 하며, 이 함수를 사용하지 않고 C++ 문장을 이용해서 공유되는 값을 수정하는 스레드가 있어서는 안 된다.&lt;/li&gt;
&lt;li&gt;인터락 함수들은 수행 중인 CPU 플랫폼마다 서로 다르게 동작된다. x86 계열의 CPU라면 인터락 함수들은 버스에 하드웨어 시그널을 실어서 다른 CPU가 동일 메모리 주소에 접근하지 못하도록한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인터락 함수들은 매우 빠르게 동작하기 때문에 중요하다.&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;수행을 완료하는데 단 몇 CPU 사이클만을 필요로하며 유저 모드와 커널 모드 간의 전환도 발생하지 않는다.&lt;/li&gt;
&lt;li&gt;InterlockedExchange는 특별히 스핀락을 구현해야 하는 경우에 매우 유용하게 사용될 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스핀락 : 임계 영역에 다른 스레드가 들어와 있으면 while 과 같은 루프문을 회전하게 하는 것&lt;/li&gt;
&lt;li&gt;스핀락과 같은 기법은 CPU 시간을 많이 낭비할 수 있기 때문에 세심한 주의가 필요하지만 수행 시간이 짧은 영역에서는 빠르게 동작할 수 있는 기법이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;락 변수와 락을 통해 보호받고자 하는 데이터는 서로 다른 캐시 란이에 있도록하는 것이 좋다. 만일 락 변수와 데이터가 동일한 캐시 라인에 있게 되면, 리소스를 사용 중인 CPU는 동일 리소스에 접근하고자 하는 다른 CPU와 경쟁하게 될 것이다. 이것은 성능에 나쁜 영향을 미치게 된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스핀락은 보호된 리소스가 매우 짧은 시간 동안만 사용될 것이라고 가정한다. 따라서 일차적으로 수회 스핀(루프 회전)을 수행해 보고 그때까지도 리소스에 접근이 불가능하면 커널 모드로 스레드를 전환해서 대기하는 것이 좀 더 효과적이다. (4000회 정도) (크리티컬 섹션의 구현 방식이기도 하다)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;값을 읽기만 하는(변경하지 않고) 인터락 함수는 필요하지 않기 때문에 존재하지 않느다.&lt;/li&gt;
&lt;li&gt;인터락 함수는 메모리 맵 파일과 같이 공유 메모리 영역에 존재하는 값을 여러 프로세스 사이에서 동기적으로 접근하기 위해 사용되기도 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2. 캐시 라인
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU가 메모리로부터 값을 가져올 때는 바이트 단위로 값을 가져오는 것이 아니라 캐시 라인을 가득 채울 만큼 충분한 양을 한 번에 가여온다.&lt;/li&gt;
&lt;li&gt;캐시 라인은 32, 64, 128바이트 크기로 구성되며(CPU에 따라 다르다), 각기 바이트 크기를 경계로 정렬되어 있다.&lt;/li&gt;
&lt;li&gt;동기화 할때 캐시에 있는 값을 활용하게 되면 실제로 변경한 내용이 갱신되지 않기 때문에 치명적인 오류가 생긴다.&lt;/li&gt;
&lt;li&gt;이러한 특성 때문에 애플리케이션이 사용하는 데이터는 캐시 라인의 크기와 그경계 단위로 묶어서 다루는 것이 좋다.&lt;/li&gt;
&lt;li&gt;volatile를 사용하면 메모리에 직접 접근해서 값을 읽기 때문에 이런 문제를 피할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;3. 고급 스레드 동기화 기법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복잡한 자료 구조에 대해 원자적 접근을 수행해야 한다면, 인터락 함수는 고려 대상이 될 수 없으며, 윈도우가 제공하는 다른 기능을 이용해야 한다.&lt;/li&gt;
&lt;li&gt;리소스가 가용하지 않거나 특별한 이벤트가 발생하지 않는다면, 시스템은 스레드를 대기 상태로 두어 CPU 시간이 낭비된느 것을 막을 수 있다.&lt;/li&gt;
&lt;li&gt;① 회피 기술
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다수의 스레드에 의해 공유되고 있는 변수의 상태를 지속적으로 폴링(polling)하여 다른 스레드가 작업을 완료했는지의 여부를 확인하는 동기화 기법이다.&lt;/li&gt;
&lt;li&gt;이 방법은 문제가 2가지 있다.&lt;/li&gt;
&lt;li&gt;이 같은 폴링 방법은 매우 간편하기 때문에 스핀락과 같은 경우 폴링 방법을 사용한다. 하지만 이보다 더 좋은 방법이 있다. 일반적으로 스핀락이나 폴링 방법은 가능한 한 사용하지 않는 것이 좋으며, 대신 스레드가 필요로 하는 자원이 가용 상태가 될 때까지 스레드를 대기 상태로 머무르게 하는 것이 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;4. 크리티컬 섹션
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공유 리소스에 대해 배타적으로 접근해야 하는 작은 코드의 집합을 의힌다.&lt;/li&gt;
&lt;li&gt;원자적이라는 의미는 현재 스레드가 리소스에 접근 중인 동안에는 다른 스레드가 동일 리소스에 접근할 수 없다는 것을 말한다.&lt;/li&gt;
&lt;li&gt;인터락 함수로 동기화 문제를 해결할 수 없는 경우라면 크리티컬 섹션을 사용하기 바란다. 크리티컬 섹션의 우수한 점은 사용하기도 쉽고 내부적으로 인터락 함수를 사용하고 있기 때문에 매우 빠르게 동작한다는 것이다. 그러나 이러한 장점에도 불구하고 서로 다른 프로세스에 존재하는 스레드 사이의 동기화에는 사용할 수 없다는 치명적인 단점이 있다.&lt;/li&gt;
&lt;li&gt;① 크리티컬 섹션 : 세부사항
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CRITICAL_SECTION 구조체를 다루기 위해서는 항상 알맞는윈도우 함수들을 사용해야하며, 구조체의 주소를 인자로 사용해야한다. 이러한 함수들은 구조체의 멤버들이 어떻게 다루어야 할지 알고 있으며, 구조체의 상태가 항시 일관되게 유지될 수 있도록 해준다.&lt;/li&gt;
&lt;li&gt;CRITICAL_SECTION 구조체는 프로세스 내의 모든 스레드가 손쉽게 접근할 수 있도록 전역변수로 선언하는 것이 일반적이지만 지역변수로도 선언될 수 있으며, 힙에 동적으로 할당될 수도 있다. 또한 클래스 정의 시에는 private 멤버로 선언한느 것이 보통이다. 여기에는 두 가지 요구 사항이 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;첫째, 공유 리소스에 접근하고자 하는 모든 스레드들은 반드시 해당 리소스를 보호하는 CRITICAL_SECTION 구조체의 주소를 알고 있어야 한다. 스레드가 구조체의 주소를 얻어오는 메커니즘은 사용자가 원하는 방식이라면 어떤 방식이든 상관없다.&lt;/li&gt;
&lt;li&gt;둘째, 스레드가 리소스 보호를 위해 이 구조체를 사용하기 전에 반드시 구조체에 대한 초기화가 선행되어야 한다.(VOID InitializeCreticalSection(PCRITICAL_SECTION pcs);&lt;/li&gt;
&lt;li&gt;프로세스의 스레드가 더 이상 공유 리소스를 사용할 필요가 없으면, 다음과 같은 함수를 호출하여 CRITICAL_SECTION을 삭제해야 한다.&amp;nbsp;&lt;br /&gt;- VOID DeleteCriticalSection (PCRITICAL_SECTION pcs);&lt;br /&gt;DeleteCriticalSection은 구조체 내의 모든 멤버변수를 리셋한다. 당연히 다른 스레드가 이 구조체를 사용하는 동안에는 절대로 크리티컬 섹션을 삭제해서는 안된다.&lt;/li&gt;
&lt;li&gt;공유 리소스에 접근하는 코드를 작성하는 경우, 해당 코드 앞쪽에서 다음 함수를 호출해야 한다.&lt;br /&gt;- VOID EnterCriticalSection(PCRITICAL_SECTION pcs);&lt;br /&gt;EnterCriticalSection은 구조체 내의 멤버변수들을 확인하여 어떤 스레드가 현재 공유 리소스를 사용하고 있는지 알아낸다. EnterCriticalSection은 내부적으로 다음과 같은 테스트를 수행한다.&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만일 공유 리소스를 사용하는 스레드가 없다면 CRITICAL_SECTION 구조체 내의 멤버변수를 갱신하여 이 함수를 호출한 스레드가 공유 자원에 대한 접근 권한을 획득했음을 설정한 후 스레드가 계속 수행될 수 있도록(공유 리소스를 사용하도록) 지체 없이 반횐한다.&lt;/li&gt;
&lt;li&gt;만일 이 함수를 호출한 스레드가 접근 권한 획득을 위해 이 함수를 몇 번 호출하였는지를 멤버변수에 기록한 후 스레드가 계속 수행될 수 있도록 지체없이 반환한다. 이러한 상황은 자주 발생하지는 않지만 스레드가 LeaveCriticalSection을 호출하지 않고 EnterCriticalSection을 연속해서 두 번 호출한 경우 발생할 수 있다.&lt;/li&gt;
&lt;li&gt;CRITICAL_SECTION 내의 멤버변수를 확인해 보았을 때 다른 스레드(이 함수를 호출한 스레드가 아닌)가 이미 공유 리소스에 대한 접근 권한을 획득한 상태라면 EnterCriticalSection은 이 함수를 호출한 스레드를 이벤트 커널 오브젝트를 이용하여 대기 상태로 만든다. 스레드가 대기 상태과 되면 CPU 시간을 낭비하지 않기 때문에 효율적이다. 시스템은 EnterCriticalSection 함수를 호출한 스레드가 리소스에 접근하고자 함을 기억하고 있으며, 현재 공유 리소스를 사용 중인 스레드가 LeaveCriticalSection을 호출하면 자동적으로 CRITICAL_SECTION 멤버변수를 갱신하여 대기 중인 스레드를 스케줄 가능하도록 만든다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;EnterCriticalSection은 복잡한 작업을 수행하지는 않으며, 몇 가지 간단한 테스트 정도의 일만 수행한다. 이 함수가 가지 있는 이유는 이러한 테스트들이 모두 원자적으로 수행된다는데 있다.&lt;/li&gt;
&lt;li&gt;EnterCriticalSection이 스레드를 대기 상태로 전환하면 이러한 스레드는 아주 오랫동안 스케줄될 수 없게 된다. 가끔 잘못 작성된 애플리케이션의 경우 대기 상태로 전환되 스레드가 다시 스케줄 가능 상태로 돌아오지 못하는 경우도 있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;이러한 스레드를 기아 상태의 스레드라고 한다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;EnterCriticalSection 대신 다음 함수를 사용할 수도 있다.&lt;br /&gt;- BOOL TryEnterCriticalSection(PCRITICAL_SECTION pcs);&lt;br /&gt;이 함수를 호출한 스레드를 절대 대기 상태로 진입시키지는 않고 반환 값으로 리소스에 대한 접근 권한을 얻었는지의 여부를 가져온다. 이미 사용중이면 FALSE를 아니면 TRUE. TRUE를 반환하는 경우 CRITICAL_SECTION의 멤버변수를 현재 스레드가 공유 리소스의 접근 권한을 획득한 것으로 갱신하게된다. 따라서 TryEnterCriticalSection이 TRUE를 반환하는 경우에는 반드시 LeaveCriticalSection을 호출해야 한다.&lt;/li&gt;
&lt;li&gt;공유 리소스에 대한 사용을 마치면 반드시 다음 함수를 호출해야 한다.&lt;br /&gt;- VOID LeaveCriticalSection(PCRITICAL_SECTION pcs);&lt;br /&gt;CRITICAL_SECTION 구조체 내의 멤버변수를 확인하고, 공유 리소스에 대해 접근 권한을 획득한 횟수를 1만큼 감소시킨다. 만일 이 값이 0보다 크면 LeaveCriticalSection은 아무런 작업도 수행하지 않고 반환된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;② 크리티컬 섹션과 스핀락
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 스레드가 이미 진입한 크리티컬 섹션에 특정 스레드가 재진입을 시도하면, 스레드는 바로 대기 상태로 변경된다. 이것은 스레드가 유저 모드에서 커널 모드로 전환되어야 함을 의미하며, 전환 과정은 매우 값비싼 동작에 해당한다.&lt;/li&gt;
&lt;li&gt;재진입을 시도한 스레드를 커널 모드로 완전히 전환하기도 전에 수행 중이던 스레드가 공유 리소스의 소유권을 반환할 수도 있다. 이런 경우 상당한 CPU 시간이 낭비된다.&lt;/li&gt;
&lt;li&gt;크리티컬 섹션의 성능을 개선하기 위해 마이크로소프트는 크리티컬 섹션에 스핀락 메커니즘을 도입하였다. 즉, EnterCriticalSection이 호출되면 일정 횟수 동안 스핀락을 이용하여 리소스 획득을 시도하는 루프를 수행하도록 하였다. 스핀락을 수행하는 동안 공유 리소스에 대한 획득에 실패한 경우에만 스레드를 대기 상태로 전환하기 위해 커널 모드로의 전환을 시도하도록 변경하였다.&lt;/li&gt;
&lt;li&gt;크리티컬 섹션에 스핀락을 사용하려면 크리티컬 섹션 초기화 시 다음함수를 사용해야 한다.&lt;br /&gt;- BOOL InitializeCriticalSectionAndSpinCount(PCRITICAL_SECTION pcs, DWORD dwSpinCount);&lt;br /&gt;dwSpinCount의 값으로 스핀락 루프의 횟수를 전달한다(0~0x00FFFFFF)&lt;/li&gt;
&lt;li&gt;크리티컬 섹션의 스핀 횟수는 다음 함수를 호출하여 변경할 수 있다.&lt;br /&gt;- DWORD SetCriticalSectionSpinCount(PCRITICAL_SECTION pcs, DWORD dwSpinCount);&lt;/li&gt;
&lt;li&gt;조언을 하자면 프로세스 힙에 대한 접근을 보호하기 위해 사용하는 크리티컬 섹션의 스핀 카운트는 대략 4000이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;③ 크리티컬 섹션과 에러 처리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;InitializeCreticalSection가 실패할 가능성이 있지만 에러 발생 가능성에 대해 전혀 생각하지 않았기 때문에 반환값이 VOID이다. InitializeCreticalSectionAndSpinCount 함수를 사용하면 이런 문제를 해결할 수 있다. 메모리 블록 할당에 실패할 경우 FALSE를 반환한다.&lt;/li&gt;
&lt;li&gt;일단 이벤트 커널 오브젝트가 생성되면 DeleteCriticalSection 호출 시까지는 삭제되지 않을 것이기 때문에 크리티컬 섹션 사용을 마친 후에 DeleteCriticalSection 함수를 호출하는 것을 잊어서는 안된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;5. 슬림 리더-라이터 락
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SRWLock (Slim Reader-Writer Lock)은 단순 크리티컬 섹션과 유사하게 다수의 스레드로부터 단일의 리소스를 보호할 목적으로 사용된다.&lt;/li&gt;
&lt;li&gt;크리티컬 섹션과의 차이점은, SRWLock의 경우 리소스의 값을 읽기만 하는 스레드(reader)들과 그 값을 수정하려는 스레드(writer)들이 완전히 구분되어 있을 경우에만 사용할 수 있다는 것이다.&lt;/li&gt;
&lt;li&gt;공유 리소스의 값을 읽기만 하는 리더들은 동시에 리소스에 접근한다 하더라도 공유 리소스의 값을 손상시키지 않기 때문에 동시에 수행되어도 무방하다.&lt;/li&gt;
&lt;li&gt;동기화는 라이터 스레드가 리소스의 내용을 수정하려고 시도하는 경우에만 필요하며, 이 경우 리소스에 대한 베타적인 접근이 이루어져야 한다. 라이터 스레드가 리소스의 내용을 수정하는 동안에는 어떠한 리더, 라이터 스레드도 공유 리소스에 접근해서는 안된다.&lt;/li&gt;
&lt;li&gt;최상의 성능으로 동작하는 애플리케이션을 작성하고 싶다면 가장 먼저 공유 리소스를 사용하지 않도록 작성할 수 있는지 검토한다. 다음으로 인터락 API, SRWLock, 크리티컬 섹션 순으로 사용을 검토해야 한다. 이러한 방식이 구현하고자 하는 상황에 전혀 적합하지 않는 경우에만 커널 오브젝트를 사용하는 것이 좋다.(속도차이가 10배가량 난다)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;6. 조건변수
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조건변수(condition veriable)를 사용하면 스레드가 리소스에 대한 락을 해제하고 SleepConditionVariableCS나 SleepConditionVariableSRW 함수에서 지정한 상태가 될 때까지 스레드를 브로킹해 준다. 또한 이러한 동작이 원자적으로 수행되도록 설계되어 개발 업무를 좀 더 간편하게 해준다.&lt;/li&gt;
&lt;li&gt;Monitor 클래스와 조건변수는 매우 유사하다. 이 둘은 리소스에 대한 동기적인 접근을 위해 각기 SleepConditionVariable과 Wait 함수를 제공하고, 시그널 기능을 위해 WakeConditionVariable과 Pusle(All) 기능을 제공한다.&lt;/li&gt;
&lt;li&gt;① Queue 예제 애플리케이션&lt;/li&gt;
&lt;li&gt;② 유용한 팁과 테크닉
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;크리티컬 섹션이나 리더-라이터 락과 같은 락을 사용할 때에는 반드시 사용해야 하거나 절대 사용하지 말아야 할 것이 있다. 다음에 락을 사용할 경우에 활용할 수 있는 몇 가지 유용한 팁과 테크닉을 설명하였다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원자적으로 관리되어야 하는 오브젝트 집합당 하나의 락만을 사용하라
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애플리케이션에서 사용되는 논리적 단일 리소스들은 모두 자신만의 락을 가지고 있어야 하며, 논리적 단일 리소스 전체 혹은 논리적 단일 리소스 내의 일부 리소스에만 접근하는 경우에도 이러한 락을 이용해서 동기화를 수행해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;다수의 논리적 리소스들에 동시에 접근하는 방법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다수의 리소스에 락을 설정하는 순서를 항상 동일하게 유지하도록 코드를 작성해야 한다.&lt;/li&gt;
&lt;li&gt;LeaveCriticalSection의 호출 순서는 문제가 되지 않는데, 이는 이 함수가 스레드를 대기 상태로 변경하지 않기 때문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;락을 장시간 점유하지 마라
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;락을 너무 오랜 시간 점유하고 있게 되면 다른 스레드들이 계속 대기 상태에 머물러 있게 되기 때문에 애플리케이션의 성능에 나쁜 영향을 미칠 수 있다.&lt;/li&gt;
&lt;li&gt;락 내부에 오래걸리는 작업(커널 모드 등)을 수행하지 마라&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>프로그래밍 서적/Window via C++</category>
      <author>LEDPEAR</author>
      <guid isPermaLink="true">https://ledpear.tistory.com/123</guid>
      <comments>https://ledpear.tistory.com/123#entry123comment</comments>
      <pubDate>Sun, 7 Nov 2021 18:02:02 +0900</pubDate>
    </item>
    <item>
      <title>Window via C/C++ 6장 - 스레드의 기본</title>
      <link>https://ledpear.tistory.com/122</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0. 개요
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 프로세스는 적어도 하나 이상의 스레드를 사용한다&lt;/li&gt;
&lt;li&gt;운영체제가 스레드를 다루기 위해 사용하는 스레드 커널 오브젝트, 스레드 커널 오브젝트는 시스템이 스레드에 대한 통계 정보를 저장하는 공간이기도 하다&lt;/li&gt;
&lt;li&gt;스레드가 코드를 수행할 때 함수의 매개변수와 지역변수를 저장하기 위한 스레드 스택&lt;/li&gt;
&lt;li&gt;프로세스는 스스로 수행될 수 없고 단순히 생각한다면 스레드의 저장소로 볼 수도 있다.&lt;/li&gt;
&lt;li&gt;프로세스는 자신만의 주소공간을 가지기 때문에 스레드에 비해 더 많은 시스템 리소스를 사용한다. 프로세스별로 가상 주소 공간을 생성하는 것은 매우 많은 시스템 리소스를 필요로 한다. 특히 개별 프로세스는 상당량의 정보를 시스템 내부에 저장해 두어야 하기 때문에 메모리를 많이 필요로 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;1. 스레드를 생성해야 하는 이유
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드는 프로세스 내의 수행 흐름을 의미한다.&lt;/li&gt;
&lt;li&gt;프로세스의 초기화가 진행되는 동안에 시스템은 주스레드를 생성한다.&lt;/li&gt;
&lt;li&gt;애플리케이션이 마이크로소프트 C/C++ 컴파일러로 작성된 경우라면, 주 스레드는 C/C++ 런타임 라이브러리의 시작 코드를 수행하는 것으로 시작된다. 이후 진입점 함수(_tmain이나 _tWinMain)를 호출하고 이 함수가 반환될 때까지 수행을 계속하다가 함수가 반환되면 C/C++ 런타임 라이브러리의 시작 코드가 ExitProcess를 호출하여 수행을 종료한다.&lt;/li&gt;
&lt;li&gt;CPU를 유휴 상태로 놔둬야 할 필요는 없으며, 계속해서 CPU가 작업을 수행할 수 있도록 해 주는 것이 좋다.&lt;/li&gt;
&lt;li&gt;각 스레드별로 전용 CPU를 할당하는 것이 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2. 스레드를 생성하지 말아야 하는경우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드는 상당히 유용하지만. 이전의 방식으로 풀던 문제를 다수의 스레드를 이용하여 해결하려 하면 새로운 문제를 일으키는 경우도 있다(프린트 출력과, 문서 수정을 동시에 하는 경우)&lt;/li&gt;
&lt;li&gt;일반적으로, 애플리케이션은 모든 윈도우를 생성하고 GetMessage 루프를 가지고 있는 단일 사용자 인터페이스 스레드를 가지고 있다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;윈도우 탐색기는 각각의 폴더 윈도우에 대해 서로 다른 스레드를 생성한다. 이를 통해 파일을 복사하거나 다른 폴더를 계속 탐색할 수 있다.&lt;/li&gt;
&lt;li&gt;다수의 스레드를 이용하여 사용자 인터페이스를 다루는 방식은 매우 신중하게 사용되어야 하며 가능한 한 사용하지 않는것이 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;3. 처음으로 작성하는 스레드 함수
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 스레드는 수행을 시작할 진입점 함수를 반드시 가져야 한다.&lt;/li&gt;
&lt;li&gt;주 스레드의 진입점 함수는 _tmain이나 _tWinMain이다.&lt;/li&gt;
&lt;li&gt;프로세스 내에 두 번째 스레드를 만들려면 새로 생성되는 스레드는 아래와 같은 형태의 진입점 함수를 반드시 가져야 한다.&amp;nbsp;&lt;/li&gt;
&lt;li id=&quot;code_1634263174525&quot; contenteditable=&quot;false&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;span&gt;DWORD WINAPI&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #a31515;&quot;&gt;ThreadFunc&lt;/span&gt;&lt;span&gt;(PVOID pvParam)&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;{ DWORD dwResult =&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;; ...&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;return&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(dwResult); }​&lt;/li&gt;
&lt;li&gt;스레드 함수가 반환되는 시점에 스레드는 수행을 멈추고 스레드가 사용하던 스택도 반환된다. 또한 스레드 커널 오브젝트의 사용 카운트도 감소한다. 이 값이 0이 되면 스레드 커널 오브젝트는 파괴된다. 프로세스 커널 오브젝트와 마찬가지로 스레드 커널 오브젝트 또한 이를 통해 관리되는 스레드만큼 살아 있으며 스레드 종료 이후에도 여전히 살아 있을 수 있다.&lt;/li&gt;
&lt;li&gt;스레드 함수에 대해 몇가지 중요한 점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주 스레드의 진입점 함수의 이름이 기본적으로 main, wmain, WinMain, 또는 wWinMain( 예외적으로 링커의 /ENTRY: 옵션을 사용하면 진입점 함수의 이름을 임의로 변경할 수 있다)이어야 하는것과 다르게 스레드 함수는 어떠한 이름이라도 사용될 수 있다. 실제로 애플리케이션에 여러개의 스레드 함수가 필요하다면 각각은 서로 다른 이름으로 명명되어야만 한다.&lt;/li&gt;
&lt;li&gt;주 스레드의 진입점 함수에는 문자열 매개변수가 전달되어야 하기 때문에 ANSI 버전과 유니코드 버전의 진입점 함수가 main/wmain, WinMain/wWinMain처럼 각각 존재하게 된다. 하지만 스레드 함수는 하나의 매개변수만 전달할 수 있고, 매개변수의 의미는 운영체제가 아니라 사용자에 의해 정의되기 때문에 반드시 ANSI와 유니코드 버전을 각기 구성해야하는 것은 아니다.&lt;/li&gt;
&lt;li&gt;스레드 함수는 반드시 값을 반환해야 한다. 이 값은 나중에 스레드의 종료 코드가 된다. 이것은 C/C++ 런터임 라이브러리가 주 스레드의 종료 코드를 프로세스의 종료 코드로 사용하는 것과 유사하다.&lt;/li&gt;
&lt;li&gt;스레드 함수는 가능한 한 함수로 전도리된 매개변수와 지역 변수만을 사용하도록 작성되는 것이 좋다. 만일 정적 변수나 전역 변수를 사용하게 되면 다수의 스레드가 동시에 변수에 접근할 수 있게 되며(Thread Safe하지 않게 된다), 이는 변수의 값이 잘못 변경되는 원인이 되기도 한다. 하지만 함수의 매개변수와 지역변수는 스레드의 스택에 유지되기 때문에 다른 스레드에 의해 내용이 변경될 가능성이 거의 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;4. CreateThread 함수
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1636275570244&quot; class=&quot;c++ arduino&quot; style=&quot;overflow: auto; margin: 28px auto; padding: 0px; color: #555555; background-color: #f6f7f8; overflow-wrap: break-word; font-family: Menlo, Consolas, Monaco, monospace; font-size: 14px; line-height: 20px;&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;HANDLE CreateThread(
	PSECURITY_ATTRIBUTES pas,
    DWORD cbStackSize,
    PTHREAD _START_ROUTINE pfnStartAddr,
    PVOID pvParam,
    DWORD dwCreateFlags,
    PDWORD pdwThreadID);&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;윈도우가 제공하는 CreateThread 함수는 스레드를 생성하는 함수이다. 하지만 C/C++로 코드를 작성하는 경우에는 CreateThread를 사용해서는 안되고, 마이크로소프트 C/C++ 런타임 라이브러리에서 제공하는 _begin-threadex 함수를 사용해야 한다. 다른 컴파일러에서도 CreateThread 함수를 대체할 만한 함수를 제공할 것이며, 반드시 컴파일러에 의해 제공되는 다른 함수를 이용해야 한다.&lt;/li&gt;
&lt;li&gt;CreateThread가 호출되면 시스템은 스레드 커널 오브젝트를 생성한다. 이 스레드 커널 오브젝트는 스레드 자체는 아니며, 운영체제가 스레드를 다루기 위한 조그만 데이터 구조체이다.&lt;/li&gt;
&lt;li&gt;다음으로, 시스템은 스레드가 사용할 스택을 확보한다. 새로운 스레드는 스레드를 생성한 프로세스와 동일한 컨텍스트 내에서 수행된다. 따라서 새로운 스레드는 프로세스의 모든 커널 오브젝트 핸들뿐만 아니라 프로세스에 있는 모든 메모리, 그리고 같은 프로세스에 있는 다른 모든 스레드의 스택에 조차 접근이 가능하다.&lt;/li&gt;
&lt;li&gt;다음은 CreateThread 함수의 각 매개변수들에 대한 설명이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;psa&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SECURITY_ATTRIBUTES 구조체를 가리키는 포인터다.&lt;/li&gt;
&lt;li&gt;스레드 커널 오브젝트에 대해 기본 보안 특성을 사용할 것이라면 이 매개변수로 NULL을 전달하면 된다(대부분 그렇게 사용)&lt;/li&gt;
&lt;li&gt;만일 차일드 프로세스로 해당 스레드 커널 오브젝트 핸들을 상속하로독 하려면 SECURITY_ATTRIBUTES 구초제의 bInhertHandle 멤버를 TRUE로 초기화하여 그 포인터를 전달하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;cbStackSize
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드가 자신의 스택을 위해 얼마만큼의 주소 공간을 사용할지를 지정한다.&lt;/li&gt;
&lt;li&gt;CreateProcess를 호출하여 프로세스가 시작되면 내부적으로는 CreateThread 함수를 호출하여 프로세스의 주 스레드를 초기화하는데, 이때 CreateProcess는 실행 파일 내부에 저장되어 있는 값을 이용하여 cbStackSize 매개변수의 값을 결정한다. 실행 파일 내에 저장되어 스택의 크기를 변경하기 위해서는 링커의 /STACK 스위치를 사용하면 된다.&lt;/li&gt;
&lt;li&gt;/STACK:[ reserve] [ , commit]&lt;/li&gt;
&lt;li&gt;reserve 인자는 시스템이 스레드 스택을 위해 지정된 크기만큼의 주소 공간을 예약하게 한다. 기본값은 1MB다.&lt;/li&gt;
&lt;li&gt;commit 인자는 스택으로 예약된 주소 공간에 커밋된 물리적 저장소의 초기 크기를 나타낸다. 기본값은 한 페이지 크기다.&lt;/li&gt;
&lt;li&gt;스레드가 코드를 수행함에 따라 한 페이지 이상의 스택을 필요로 할 수도 있을 텐데, 이경우 스레드의 스택 오버플로 예외를 발생시키게 된다.&lt;/li&gt;
&lt;li&gt;시스템은이러한 예외가발생하며 추가적인 페이지를 예약된 주소 공간상에 커밋해 준다. 이러한 방식으로 스레드가 사용하는 스택을 필요 시 동적으로 커지게 된다.&lt;/li&gt;
&lt;li&gt;cbStackSize에 0 이외의 값을 지정하면 지정된 크기의 메모리를 에약하고 커밋까지 수행한다.&lt;/li&gt;
&lt;li&gt;스택에서 사용할 예약된 영역은 /STACK 링커 스위치와 cbStackSize에 지정된 값 중 큰 값을 이용한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;커밋할 저장소의 크기는 항시 cbStackSize 값을 따른다. 하지만 cbStackSize 매개변수로 0을 전달하면 CreateThread는 /STACK 링커 스위치를 이용하여 실행 파일 내에 포함된 커밋된 물리적 저장소의 초기 크기를 따르게 된다.&lt;/li&gt;
&lt;li&gt;예약된 영역의 크기는 스택으로 사용할 수 있는 공간의 최대 크기를 지정하는 것이기 때문에 재귀 호출을 끝없이 반복하게 되는 경우 예외가 발생할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;pfnStartAddr과 pvParam
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;pfnStartAddr : 새로이 생성되는 스레드가 호출할 스레드 함수의 주소를 가리킨다. 이 스레드 함수의 pvParam 매개변수로는 CreateThread 함수의 pvParam 매개변수로 전달한 값이 그래도 전달된다.&lt;/li&gt;
&lt;li&gt;이 매개변수는 스레드 함수에 초기 값을 전달하는 용도로 사용될 수 있다. 전달되는 값은 단순 숫자 값일 수도 있고, 다양한 형태의 값을 포함하는 데이터 구조체의 포인터가 될 수도 있다.&lt;/li&gt;
&lt;li&gt;다수의 스레드가 동일한 스레드의 함수 주소를 진입점을 ㅗ사용하는 것은 매우 유용한 방법이다.&lt;/li&gt;
&lt;li&gt;윈도우는 선점형 멀티스레딩 시스템이다. 때문에 다수의 스레드가 동시에 수행되면 새로운 문제가 발생할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;dwCreateFlags
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드를 생성할 때 세부적인 제어를 수행하기 위한 추가적인 플래그를 지정하는 데 사용한다.&lt;/li&gt;
&lt;li&gt;이 값으로 0을 전달하면 스레드는 생성되는 즉시 CPU에 의해 스케줄 가능하게 된다.&lt;/li&gt;
&lt;li&gt;CREATE_SUSPENDED를 전달하면 시스템은 스레드를 생성하고 초기화를 완료한 이후 CPU에 의해 바로 스케줄되지 않도록 일시 정지 상태를 유지하게 된다.&lt;/li&gt;
&lt;li&gt;CREATE_SUSPENDED 플래그를 사용하면 애플리케이션이 새로 생성되는 스레드가 코드를 수행하기 전에 스레드와 관련된 값들을 변경할 수 있는 기회를 가지게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;pdwThreadID
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로운 스레드에 할당되는 스레드 ID 값을 저장할 DWORD 변수를 가리키는 주소를 지정하면 된다.&lt;/li&gt;
&lt;li&gt;이 매개변수 값으로 NULL을 전달할 수도 있는데(보통 이렇게 한다.) 이렇게 하면 스레드의 ID에 대해서는 관심이 없다고 함수에게 알려주게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;5. 스레드의 종료
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드는 4가지 방법으로 종료될 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드 함수가 반환된다. (이 방법을 강력히 추천한다)&lt;/li&gt;
&lt;li&gt;스레드 함수 내에서 ExitTheard 함수를 호출한다. (이 방법은 피하는 것이 좋다.)&lt;/li&gt;
&lt;li&gt;동일한 프로세스나 다른 프로세스에서 TerminateThread 함수를 호출한다 (이 방법도 피하는 것이 좋다)&lt;/li&gt;
&lt;li&gt;스레드가 포함된 프로세스가 종료된다 (이 방법도 피하는 것이 좋다)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스레드 함수 반환
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드를 종료하려는 경우 항상 스레드 함수가 반환되도록 설계하는 것이 좋다. 이것은 스레드가 사용한 자원을 적절하게 정리할 수 있는 유일한 방법이다..&lt;/li&gt;
&lt;li&gt;스레드 함수가 반환되면 다음과 같은 작업을 수행한다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드 함수 내에서 생성한 모든 C++ 오브젝트들은 파괴자를 통해 적절히 제거된다.&lt;/li&gt;
&lt;li&gt;운영체제는 스레드 스택으로 사용하엿던 메모리를 반환한다.&lt;/li&gt;
&lt;li&gt;시스템은 스레드의 종료 코드를 스세드 함수의 반환 값으로 설정한다 (이 값은 스레드 커널 오브젝트 내에 저장된다.)&lt;/li&gt;
&lt;li&gt;시스템은 스레드 커널 오브젝트의 사용 카운트를 감소시킨다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ExitThread 함수
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ExitThread 함수를 호출하여 스레드를 강제로 종료할 수 있다.&lt;/li&gt;
&lt;li&gt;VOID ExitThread (DWORD dwExitCode);&lt;/li&gt;
&lt;li&gt;이 함수는 스레드를 강제로 종료하고 운영체제가 스레드에서 사용했던 모든 운영체제 리소스를 정리하도록 한다. 하지만 C/C++ 리소스(C++ 클ㄹ래스 오브젝트와 같은)는 정리되지 않느다.&lt;/li&gt;
&lt;li&gt;따라서 ExitThread함수를 호출하기 보다는 스레드 함수를 반환하도록 코드를 작성하는 것이 더 좋다.&lt;/li&gt;
&lt;li&gt;물론 ExitThread 함수의 dwExitCode 매개변수를 이용하여 스레드의 종료 코드를 설정할 수 있다. ExitThread 함수는 반환되지 않는 함수이기 때문에 이후에 나온느 코드는 수행되지 않는다.&lt;/li&gt;
&lt;li&gt;C/C++로 코드를 작성하는 경우라면 ExitTheard를 사용하는 대신 _endthreadex 함수는 사용하는 것이 좋고, 다른 컴파일러를 사용한다면 해당 컴파일러가 제공하는 대체 함수를 사용하는 것이 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TerminateThread 함수
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;BOOL TerminateThread ( HANDLE hThread, DWORD dwExitCode);&lt;/li&gt;
&lt;li&gt;ExitThread 함수가 이 함수를 호출하는 스레드를 종료하는 것과는 다르게 TerminateThread 함수는 어떠한 스레드라도 종료할 수 있다&lt;/li&gt;
&lt;li&gt;hThread 매개변수는 종료할 스레드의 핸들을 가리킨다.&lt;/li&gt;
&lt;li&gt;스레드가 종료되면 스레드의 종료 코드는 dwExiteCode 매개변수로 전달한&amp;nbsp; 값으로 설정되고 스레드 커널 오브젝트의 사용 카운트는 감소한다.&lt;/li&gt;
&lt;li&gt;TerminateThread 함수는 비동기 함수이므로 함수가 반환되기 이전에 해당 스레드가 종료되엇음을 보장할 수없다. 만일 정확히 스레드가 종료되는 시점을 알고 싶다면 WaitForSingleObject나 이와 유사한 함수에 스레드 핸들을 전달하면 된다.&lt;/li&gt;
&lt;li&gt;스레드가 반환을 통해 혹은 ExitThread 함수를 호출해서 종료되는 경우라면 스레드가 사용하던 스택이 정상적으로 정리되지만 TherminateThread 함수를 사용하면 시스템은 종료된 스레드를 소유하고 있던 프로세스가 살아있는 동안 그 스레드가 사용하였던 스레드 스택을 정리하지 않는다.&lt;/li&gt;
&lt;li&gt;또한 일반적으로 스레드가 종료되면 DLL은 스레드 종료 통지를 받게 되지만, TerminateThread를 사용하여 스레드를 강제 종료하면 DLL은 어떻나 통지도 전달받지 못하기 때문에 적절한 정리 작업을 수행하지 못할 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;프로세스가 종료되면
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ExitProcess와 TerminateProcess 함수를 호출하는 경우에도 스레드는 종료된다. 차이점이라면 이러한 함수들을 호출하면 프로세스가 소유하고 있던 모든 스레드가 종료된다는 것이다.&lt;/li&gt;
&lt;li&gt;전체 프로세스가 종료되기 때문에 프로세스가 사용하던 리소스들도 모두 정리된다. 따라서 스레드들이 사용하던 스택들도 정리될 것이다. 프로세스를 강제 종료하면 프로세스 내에 남아 있는 스레드들에 대해 각각 TerminateThread 함수가 호출된다. 이와 같이 프로세스를 종료하게 되면 C++ 파괴자가 호출되지도 못하고 디스크로 자료를 저장하는 등의 적절한 정리 작업도 수행되지 못한다.&lt;/li&gt;
&lt;li&gt;애플리케이션의 진입점 함수가 반환되면 C/C++ 런타임 라이브러리의 시작 코드는 ExitProcess를 호출하게 된다. 만일 애플리케이션 내에서 여러개의 스레드가 동시 수행 중이었다면, 주 스레드가 종료되기 전에 각각의 스레드들에 대해 적절한 정리 작업이 수행되어야 할 것이다. 그렇지 않으면 수행 중인 다른 스레드들이 갑작스럽게 그리고 조용히 종료될 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스레드가 종료되면
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드가 종료되면 다음과 같은 작업들이 수행된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드가 소유하고 있던 모든 유저 오브젝트 핸들이 삭제된다. 윈도우에서는 대부분의 오브젝트들이 스레드에 의해서 생성되지만 (오브젝트를 생성한 스레드를 포함하고 있는) 프로세스에 의해 소유된다. 그런데 윈도우와 윈도우 훅 두 개의 사용자 오브젝트는 스레드에 의해 소유도니다. 스레드가 종료되면 시스템은 자동적으로 해당 스레드가 생성한 윈도우를 파괴하고, 설치한 윈도우 훅을 제거한다. 다른 형태의 오브젝트들은 모두 소유하고 있는 프로세스가 종료되는 시점에 파괴된다.&lt;/li&gt;
&lt;li&gt;스레드의 종료 코드는 STILL_ACTIVE에서 ExitThread나 TerminateThread에서 지정한 종료 코드로 변경된다.&lt;/li&gt;
&lt;li&gt;스레드 커널 오브젝트의 상태가 시그널 상태로 변경된다.&lt;/li&gt;
&lt;li&gt;종료되는 스레드가 프로세스 내의 마지막 활성 스레드라면 시스템은 프로세스도 값이 종료되어야 하는 것으로 간주한다.&lt;/li&gt;
&lt;li&gt;스레드 커널 오브젝트의 사용 카운트가 1만큼 감소한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스레드가 종료된다 하더라도 스레드 커널 오브젝트는 핸들을 삭제하지 않는 이상 자동적으로 파괴되지 않는다.&lt;/li&gt;
&lt;li&gt;BOOL GetExitCodeThread( HANDLE hThread, PDWORD pdwExitCode);&lt;br /&gt;종료된 스레드의 종료 코드를 획득해 오는 함수. 종료 코드는 pdwExitCode가 가리키는 DWORD 포인터를 통해 값을 반환한다.&lt;/li&gt;
&lt;li&gt;미처 종료되지 않았다면 STILL_ACTIVE, 함수가 성공적으로 호출되면 TRUE의 값이 반환될 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #5c5c5c;&quot;&gt;6. 스레드의 내부&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드는 자신만의 가상 메모리 공간을 가지지 않으므로 스택으로 활용할 메모리는 프로세스의 주소 공간으로부터 할당된다.&lt;/li&gt;
&lt;li&gt;스레드 스택은 항상 상위 메모리로부터 하위 메모리 순으로 사용된다.&lt;/li&gt;
&lt;li&gt;각 스레드는 자신만의 CPU 레지스터 세트를 가지는데, 이를 스레드 컨텍스트라고 부른다. 이러한 컨텍스트는 스레드가 마지막으로 수행되엇을 당시의 스레드의 CPU 레지스터 값을 가지고 있다.&lt;/li&gt;
&lt;li&gt;스레드의 CPU 레지스터 세트는 CONTEXT 구조체(WinNT.h 헤더 파일에 정의되어 있다) 형태로 스레드 커널 오브젝트 내에 저장된다.&lt;/li&gt;
&lt;li&gt;IP 레지스터와 SP 레지스터는 스레드 컨텍스트에 저장되는 값 중에서 가장 중요한 레지스터다. 스레드는 항상 프로세스의 컨텍스트 내부에서 수행된다. 두 레지스터의 값은 프로세스 메모리 공간 상의 특정위치를 가리키고 있다.&lt;/li&gt;
&lt;li&gt;SP (stack pointer 레지스터) : pfnStartAddr를 저장&lt;/li&gt;
&lt;li&gt;IP (instruction pointer 레지스터) : NTDLL.dll 모듈이 익스포트하고 있는 RtlUserThreadStart라는 문서화되지 않은 함수의 주소를 가리킨다.&lt;/li&gt;
&lt;li&gt;새로운 스레드가 RtlUserThreadStart 함수를 호출하면 다음과 같은 작업이 수행된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드 함수 내에서 예외가 발생했을 경우 시스템이 제공하는 기본적인 예외 처리 코드를 수행할 수 있도록 구조적 예외 처리(structured exception handing / SEH) 프레임이 설정된다.&lt;/li&gt;
&lt;li&gt;시스템은 CreateThread 함수 호출 시 전달한 pvParam 매개변수로 스레드 함수를 호출한다.&lt;/li&gt;
&lt;li&gt;스레드 함수가 반환되면 RtlUserThreadStart 함수는 스레드 함수가 반환한 값을 인자로 ExitThread 함수를 호출한다. 스레드 커널 오브젝트의 사용 카운트는 점차 감소되고, 스레드는 수행을 종료한다.&lt;/li&gt;
&lt;li&gt;만일 스레드가 예외를 유발하고 이러한 예외가 처리되지 않으면 RTLUserThreadStart 함수가 설정한 SEH 프레임이 예외를 처리하게 된다. 이때 사용자에게 메시지&amp;nbsp; 박스를 출력하는데, 사용자가 프로그램 닫기를 선택하면 RtlUserThreadStart는 ExitProcess를 호출하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;예외를 유발한 스레드뿐만 아니라 전체 프로세스를 종료시켜 버린다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;RtlUserThreadStart 함수는 C/C++ 런타임 라이브러리의 시작 코드를 호출하여 각종 초기화를 진행하과, _tmain이나 _tWinMain과 같은 진입점 함수를 호출한다. 진입점 함수가 반환되면 C/C++ 런타임 라이브러리 시작 코드는 ExitProcess를 호출한다. 따라서 C/C++ 애플리케이션의 주 스레드는 RtlUserThreadStart 함수로 절대 반환되지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;7. C/C++ 런타임 라이브러리에 대한 고찰
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;라이브러리 파일명&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LibCMt.lib&lt;/td&gt;
&lt;td&gt;릴리즈 버전의 정적 링크 라이브러리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LibCMtD.lib&lt;/td&gt;
&lt;td&gt;디버그 버전의 정적 링크 라이브러리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MSVCRt.lib&lt;/td&gt;
&lt;td&gt;MSVCR80.dll에 대한 동적 링크를 위한 릴리즈 버전의 임포트 라이브러리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MSVCRtD.lib&lt;/td&gt;
&lt;td&gt;MSVCR80D.dll에 대한 동적 링크를 위한 디버그 버전의 임포트 라이브러리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MSVCMRt.lib&lt;/td&gt;
&lt;td&gt;매니지드와 네이티브 코드가 섞여 있는 경우에 사용하는 임포트 라이브러리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MSVCURt.lib&lt;/td&gt;
&lt;td&gt;100% MSIL 코드로 컴파일된 임포트 라이브러리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;멀티스레드 애플리케이션에서 전통적인 C 런타임 라이브러리를 사용하였을 때 나타나는 문제점 : 스레드가 다른 스레드의 전역변수 값을 변경해버릴 수 있는 문제가 나타남&lt;/li&gt;
&lt;li&gt;멀티스레드 기반의 C/C++ 프로그램이 정상적으로 동작하려면 C/C++ 런타임 라이브러리 함수들은 사용하는 각 스레드별로 적절한 구조의 데이터 블록을 생성해야 한다. 또한 C/C++ 런타임 라이브러리 함수는 다른 스레드들로부터 영향을 받지 않도록 자신을 호출한 스레드의 데이터 블록에만 접근 가능해야한다.&lt;/li&gt;
&lt;li&gt;아래에 _beginthreadex 구현부에서 주목해야 할 부분을 나타냈다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 스레드는 C/C+ 런타임 라이브러리 힙에 _tiddata 메모리 블록을 가진다.&lt;/li&gt;
&lt;li&gt;_beginthreadex 함수에 전달된 스레드 함수의 주소는 _tiddata 메모리 블록내에 저장된다. 스레드 함수에 전달할 매개변수 또한 _tiddata 메모리 블록에 저장된다.&lt;/li&gt;
&lt;li&gt;CreateThread가 호출되면 _beginthreadex의 pfnStartAddr 매개변수로 전달한 스레드 함수가 아니라 _threadstartex라는 함수가 수행하게 된다. 또한 스레드 함수로 전달할 매개변수도 _beginthreadex에 전달한 pvParam이 아니라 _tiddata 구조체의 주소다&lt;/li&gt;
&lt;li&gt;정상적인 경우 _beginthreadex는 CreateThread와 동일하게 스레드 핸들을 반환한다. 만일 문제가 발생하면 0을 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;아래에 _threadstartex에서 주목해야 할 부분을 나타내었다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로 생성된 스레드는 RtlUserThreadStart(NTDLL.dll 내에 있는)를 호출하고 곧 _threadstartex로 진입한다.&lt;/li&gt;
&lt;li&gt;_threadstartex로는 새로 생성된 스레드의 _tiddata 블록을 가리키는 주소가 매개변수로 전달된다.&lt;/li&gt;
&lt;li&gt;TlsSetValue는 이 함수를 호출하는 스레드와 매개변수로 전달되는 값을 연계시키는 운영체제 함수다. 이러한 값이 저장되는 공간을 스레드 지역 저장소(TLS)라고 한다. _threadstartex 함수는 새로 생성된 스레드와 _tiddata 블록을 연계시킨다.&lt;/li&gt;
&lt;li&gt;아무런 인자도 전달받지 않는 _callthreadstartex 함수 내에서는 사용자가 지정한 스레드 함수의 호출부를 둘러싸는 SEH 프레임을 구성한다. 이 프레임은 런타임 라이브러리와 관련된 많은 작업들을 수행하는데, 예를 들어 런타임 에러(처리되지 않은 C++ 예외)와 C/C++런타임 라이브러리의 signal 함수가 정상 동작하도록 작업을 수행한다. 이 부분은 매우 중요한데, 만일 CreateThread 함수를 이용하여 스레드를 생성한 후 C/C++ 런타임 라이브러리가 제공하는 signal 함수를 호출하게 되면 이 함수는 정상 동작하지 않는다.&lt;/li&gt;
&lt;li&gt;사용자 정의 스레드 함수가 사용자가 전달한 매개변수 값으로 호출된다. 스레드 함수의 주소와 매개변수 값은 _beginthreadex 함수 내에서 TLS에 저장하였던 _tiddata 블록을 _callthreadstartex 함수내에서 가져와서 사용한다.&lt;/li&gt;
&lt;li&gt;사용자가 지정한 스레드 함수의 반환 값을 스레드의 종료 코드가 된다. _callthreadstartex는 단순히 _threadstartex로 반환되고, 계속해서 RtlUserThreadStart로 반환되는 구조가 아님에 주목할 필요가 있다. 만일 그렇게 되면 스레드는 종료되고, 스레드의 종료 코드는 올바르게 설정될지 모르겠지만, 이러한 메모리 누수를 일으키지 않기 위해 _endthreadex라는 C/C++ 런타임 라이브러리 함수가 제공되며, 이 함수는 매개변수로 스레드 종료코드를 전달받는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;_endthreadex에 대해 주목해야할 사항을 아래에 나타냈다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;C/C++ 런타임 라이브러릴 함수인 _getpth_noexit 함수는 이 함수를 호출하는 스레드의 _tiddata 메모리 블록을 가져오기 위해 내부적으로 운영체제의 TlsGetValue 함수를 호출한다.&lt;/li&gt;
&lt;li&gt;_tiddata 블록이 삭제되고 운영체제의 ExitThread 함수가 호출되어 스레드를 실제로 파괴한다. 물론 이 과정에서 종료 코드가 전달되고 올바르게 설정된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;① 실수로 _beginthreadex 대신 CreateThread를 호출했다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드 내에서 C/C++ 런타임 라이브러리 함수를 호출하려면 _tiddata 구조체가 필요한데, 바로 이것이 문제다 (대부분의 C/C++ 런타임 라이브러리 함수들은 스레드 안전하며 _tiddata 구조체가 필요하지 않다.)&lt;/li&gt;
&lt;li&gt;스레드의 데이터 블록을 가져오려고 시도하지만 NULL이 반환될 것이다. 그러면 C/C++ 런타임 함수는 호출하는 스레드에서 사용할 _tiddata 블록을 새로 할당하고 초기화하여 사용한다.&lt;/li&gt;
&lt;li&gt;제약 사항 없이 수행될 수 있는 것 처럼보이지만 문제가 있다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;첫째, 스레드가 C/C++ 런타임 라이브러리가 제공하는 signal 함수를 사용하는 경우 구조적 예외 처리 프레임이 준비되지 않았기 때문에 이 함수를 호출하면 프로세스가 종료되어 버린다.&lt;/li&gt;
&lt;li&gt;둘째, 스레드가 _endthreadex를 호출하지 않고 종료되어 버리면, 데이터 블록을 삭제되지 않아 메모리 누수가 발생한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;② 절대 호출하지 말아야 하는 C/C++ 런타임 라이브러리 함수
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;_beginthread, _endthread&lt;/li&gt;
&lt;li&gt;매개변수의 개수가 조금 부족하기 때문에 모든기능을 제공하지 못한다&lt;/li&gt;
&lt;li&gt;_beginthread는 보안 특성을 가진 스레드를 생성할 수 없으며, 일시 정지된 상태의 스레드로 생성할 수 없고, 스레드의 ID 값을 얻을 수 없다.&lt;/li&gt;
&lt;li&gt;_endthread는 어떠한 매개변수도 가지지않기 때문에 스레드의 종료 코드는 항상 0이 된다.&lt;/li&gt;
&lt;li&gt;스레드 함수가 반환되면 _beginthread 함수가 _endthread를 호출하는 것과 같이 _beginthreadex 는 _endthreadex를 호출한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;8. 자신의 구분자 얻기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드가 자신의(또는 자신이 속한 프로세스의) 수행 환경을 변경하는 것은 매우 일반적인 작업이기 때문에 윈도우 운영체제는 스레드가 자신을 소유하는 프로세스의 커널 오브젝트나 자신을 나타는 커널 오브젝트를 손쉽게 얻을 수 있는 함수를 제공하고 있다.&lt;/li&gt;
&lt;li&gt;HANDLE GetCurrentProcess();&lt;br /&gt;HANDLE GetCurrentThread();&lt;br /&gt;이 두개의 함수는 해당 함수를 호출한 스레드를 소유하고 있는 프로세스나 스레드 자신을 나타내는 스레드 커널 오브젝트의 허위 핸들(pseudohandle)을 반환한다.&lt;/li&gt;
&lt;li&gt;① 허위 핸들을 실제 핸들로 변경하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 핸들이란 특정 스레드를 대표할 수 있는 고유의 핸들 값을 의미한다.&lt;/li&gt;
&lt;li&gt;DuplicateHandle 함수를 이용하면 변경할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>프로그래밍 서적/Window via C++</category>
      <author>LEDPEAR</author>
      <guid isPermaLink="true">https://ledpear.tistory.com/122</guid>
      <comments>https://ledpear.tistory.com/122#entry122comment</comments>
      <pubDate>Sun, 7 Nov 2021 18:00:28 +0900</pubDate>
    </item>
    <item>
      <title>Window via C/C++ 3장 - 커널 오브젝트</title>
      <link>https://ledpear.tistory.com/121</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0. 개요
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;운영체제나 우리가 개발하는 애플리케이션은 프로세스, 스레드, 파일 등과 같은 수많은 리소스를 관리하기 위해 커널 오브젝트를 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;1. 커널 오브젝트란 무엇인가?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Sysinternals에서 무료로 제공하는 툴인 WinObj를 사용하면 모든 커널 오브젝트 타입을 나열하고 확인해 볼 수 있다.&lt;/li&gt;
&lt;li&gt;함수의 이름에 포함된 오브젝트의 명칭이 반드시 커널 레벨의 오브젝트 이름과 일치하는 것은 아니다.&lt;/li&gt;
&lt;li&gt;커널(섹션?) 오브젝트 : 커널에 의해 할당된 간단한 메모리 블록. 이 메모리 블록은 커널에 의해서만 접근 가능한 구조체로 구성되어 있으며, 커널 오브젝트에 대한 세부 정보들을 저장하고 있다.&lt;/li&gt;
&lt;li&gt;마이크로 소프트는 커널 오브젝트의 구조체가 가능한 한 일관되게 유지될 수 있도록 하기 위해 메모리에 직접 접근하여 내용을 변경하지 못하게 하였다.&lt;/li&gt;
&lt;li&gt;마이크로 소프트는 정제된 방법을 통해 구조체의 내용에 접근할 수 있도록 일련의 함수 집합을 제공하고 있다.&lt;/li&gt;
&lt;li&gt;핸들 : 프로세스 내의 모든 스레드에 의해 사용 가능한 값이지만 특별한 의미를 가지고 있지는 않다. 32비트 운영체제에선 32비트, 64비트에선 64비트&lt;/li&gt;
&lt;li&gt;핸들 값들은 프로세스 별로 독립적으로 유지된다. 만일 어떤 스레드가 다른 프로세스의 스레드에게 자신의 핸들 값을 전달했을 경우, 이 핸들 값을 이용하여 수행하는 동작은 실패할 수도 있고 혹은 더 좋지 않은 결과를 초래할 수도 있다. 이는 각 프로세스별로 독립된 프로세스 핸들 테이블이 존재하고 동일한 핸들 값이라도 전혀 다른 커널 오브젝트를 참조할 수 있기 때문이다.&lt;/li&gt;
&lt;li&gt;① 사용 카운트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;커널 오브젝트는 프로세스가 아니라 커널에 의해 소유된다. 다시 말해, 만일 프로세스가 특정 함수를 통해 커널 오브젝트를 생성한 후 종료된다 하더라도 반드시 생성된 커널 오브젝트가 프로세스와 함께 삭제되는 것은 아니라는 의미이다. 다른 프로세스가 동일 커널 오브젝트를 사용하고 있다면 커널 오브젝트를 사용하는 모든 프로세스가 종료될 때까지 삭제되지 않고 남아 있는다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기억해야 할 점은 커널 오브젝트는 자신을 생성한 프로세스보다 더 오랫동안 삭제되지 않고 남아 잇을 수 있다는 것이다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;각 커널 오브젝트는 내부적으로 사용 카운트 값을 유지하고 있어 얼마나 많은 프로세스들이 사용하고 있는지 알 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;② 보안
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;커널 오브젝트는 보안 디스크립터(security descriptor)를 통해 보호될 수 있다.&lt;/li&gt;
&lt;li&gt;보안 디스크립터는 누가 커널 오브젝트를 소유하고 있으며, 어떤 그룹과 사용자들에 의해 접근되거나 사용될 수 있는지, 혹은 어떤 그룹과 사용자들에 대해 접근이 제한되어 있는지에 대한 정보를 가지고 있다.&lt;/li&gt;
&lt;li&gt;이미 존재하는 커널 오브젝트를 이용하려면(새로운 오브젝트를 생성하는 대신) 먼저 오브젝트를 이용하여 어떤 작업을 수행하려 하는지를 알려주어야 한다.&lt;/li&gt;
&lt;li&gt;상당히 많은 윈도우 함수들이 보안 정보를요구한다&lt;/li&gt;
&lt;li&gt;개발자가 저지르는 가장 큰 실수 중의 하나가 알맞는 보안 접근 플래그를 쉽게 간과한다는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>프로그래밍 서적/Window via C++</category>
      <author>LEDPEAR</author>
      <guid isPermaLink="true">https://ledpear.tistory.com/121</guid>
      <comments>https://ledpear.tistory.com/121#entry121comment</comments>
      <pubDate>Sun, 7 Nov 2021 17:58:54 +0900</pubDate>
    </item>
    <item>
      <title>Window via C/C++ 10장</title>
      <link>https://ledpear.tistory.com/120</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0. 개요
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;확장성 있는 애플리케이션이란 적은 수의 동시 작업을 수행하는 것만큼이나 호율적으로 많은 수의 동시 작업을 처리할 수 있는 애플리케이션을 말한다.&lt;/li&gt;
&lt;li&gt;서비스 애플리케이션의 경우 동시 작업이란 예측 불가능한 시점에 들어오는 클라이언트의 요청을 처리하는 것을 의미하므로 이를 위해 어느 정도의 처리 능력이 필요할지 예측할 수가 없다.&lt;/li&gt;
&lt;li&gt;스레드가 동기적인 장치 I/O를 요청하면 I/O 작업이 완료될 때까지 일시적으로 스레드의 수행이 블록킹되는데, 이처럼 스레드가 블로킹되면 다른 클라이언트의 요청을 처리하는 것과 같은 유용한 작업을 수행할 수 없으므로 수행 성능에 나쁜 영향을 미치게 된다.&lt;/li&gt;
&lt;li&gt;따라서 가능하면 스레드가 블로킹되지 않고 항상 어떤 작업을 수행하도록 하는 것이 좋으며, 스레드가 블로킹되는 상황은 가능한 한 피하는 것이 좋다.&lt;/li&gt;
&lt;li&gt;윈도우 개발자라면 I/O 컴플리션 포트의 동작 방식에 대해 완전히 이해해 둘 필요가 있다고 생각한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;1. 장치 열기와 닫기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;장치&lt;/td&gt;
&lt;td&gt;일반적인 사용 예&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;파일(File)&lt;/td&gt;
&lt;td&gt;다양한 데이터에 대한 영속적인 저장소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;디렉터리(Directory)&lt;/td&gt;
&lt;td&gt;특성과 파일 압축 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;논리적 디스크 드라이브(Logical disk drive)&lt;/td&gt;
&lt;td&gt;드라이브 포매팅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;물리적 디스크 드라이브(Physical disk drive)&lt;/td&gt;
&lt;td&gt;파티션 테이블 접근&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;직렬 포트 (serial port)&lt;/td&gt;
&lt;td&gt;전화선을 통한 데이터 전송&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;병렬 포트 (Parallel port)&lt;/td&gt;
&lt;td&gt;프린터로 데이터 전송&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메일 슬롯 (Mailslot)&lt;/td&gt;
&lt;td&gt;윈도우가 수행 중인 머신들 사이에 네트워크를 통해 일대 다 데이터 전송&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;네임드 파이프(Named pipe : 명명된 파이프)&lt;/td&gt;
&lt;td&gt;윈도우가 수행 중인 머신들 사이에 네트워크를 통해 일대일 데이터 전송&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;익명 파이프 (Anorymous pipe)&lt;/td&gt;
&lt;td&gt;단일 머신 내에서 일대일 데이터 전송 (네트워크를 통하지 않음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;소켓 (Socket)&lt;/td&gt;
&lt;td&gt;소켓을 지원하는 다양한 머신들 간에 네트워크를 통해 데이터그램이나 스트림 형태로 데이터 전송&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;콘솔(Console)&lt;/td&gt;
&lt;td&gt;텍스트 윈도우 스크린 버퍼&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;① CreateFile에 대한 세부사항 검토
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CreateFile 함수를 이용하면 디스크에 새로운 파일을 생성하거나 기존 파일에 대한 열기를 수행할&amp;nbsp; 수 있을 뿐만 아니라 파일이 아닌 다른 장치에 대해서도 열기 작업을 수행할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2. 파일 장치 이용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;윈도우에서 파일 작업을 수행할 때 가장 먼저 알아두어야 할 것은 윈도우 운영체제가 매우 큰 마일을 다룰 수 있다는 사실이다. 윈도우는 최초 설계 시부터 파일의 크기를 나타내기 위해 32비트 값이 아닌 64비트 값을 이용하였다. 따라서 이론적으로 파일의 크기는 최대 16EB(엑사바이트)가 될 수 있다.&lt;/li&gt;
&lt;li&gt;① 파일 크기 얻기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;논리적인 크기와 물리적인 크기는 다르다. 논리적인 파일의 크기는 파일의 총 크기를 나타내지만, 물리적인 크기는 압축되어 디스크를 점유하고 있는 크기를 나타낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;② 파일 포인터 위치 지정&lt;/li&gt;
&lt;li&gt;③ 파일의 끝 설정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적으로 시스템은 파일을 닫을 때 파일의 끝을 설정하는 작업을 수행한다. 하지만 때로는 파일을 닫기 전에 파일을 더 작거나 크게 변경할 필요가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>프로그래밍 서적/Window via C++</category>
      <author>LEDPEAR</author>
      <guid isPermaLink="true">https://ledpear.tistory.com/120</guid>
      <comments>https://ledpear.tistory.com/120#entry120comment</comments>
      <pubDate>Sun, 7 Nov 2021 17:57:50 +0900</pubDate>
    </item>
    <item>
      <title>Window via C/C++ 13장 - 윈도우 메모리의 구조</title>
      <link>https://ledpear.tistory.com/119</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1. 프로세스의 가상 주소 공간
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 프로세스는 자신만의 가상 주소 공간을 가지고 있다. 32비트 프로세스는 32비트 포인터를 이용하여 0x00000000부터 0xFFFFFFFF까지 표현할 수 있기 때문에 4GB의 주소 공간을 가진다. 64비트 프로세스는 64비트 포인터가 0x0000000000000000부터 0xFFFFFFFFFFFFFFFF까지의 값을 가질수 있으므로 16EB(엑사바이트)의 주소공간을 가진다.&lt;/li&gt;
&lt;li&gt;모든 프로세스들은 자신만의 주소 공간을 가지기 때문에 특정 프로세스 내에서 스레드가 수행될 때 해당 스레드는 프로세스가 소유하고 있는 메모리에 대해서만 접근이 가능하다. 다른 프로세스에 의해 소유된 메모리는 숨겨져 있으며 접근이 불가능하다.&lt;/li&gt;
&lt;li&gt;서로 다른 프로세스는 동일한 값의 주소 공간을 가질 수 있다(가상이기 때문)&lt;/li&gt;
&lt;li&gt;이 주소 공간이 물리적인 저장소가 아닌 가상의 주소 공간이라는 사실을 기억해야 한다. 주소공간이란 단순히 메모리 의 위치를 지정할 수 있는 범위를 나타내는 값이다. 애플리케이션에서 접근 예외를 유발하지 않고 성공적으로 데이터에 접근하기 위해서는 접근하고자 하는 주소 공간에 물리적 저장소가 할당되거나 매핑되어 있어야만 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2. 가상 주소 공간의 분할
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 프로세스의 가상 주소 공간은 분할되어 있으며, 각각의 분할 공간을 파티션이라고한다. 주소 공간의 분할 방식은 운영체제의 구현 방식에 따라 서로 다를 수 있으며, 윈도우 계열에서도 커널이 달라지면 조금씩 그 구조가 달라지곤한다.&lt;/li&gt;
&lt;li&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;파티션&lt;/td&gt;
&lt;td&gt;x86 32비트&lt;br /&gt;윈도우&lt;/td&gt;
&lt;td&gt;x86 32비트 윈도우,&lt;br /&gt;3GB 유저 모드&lt;/td&gt;
&lt;td&gt;x86 64비트 윈도우&lt;/td&gt;
&lt;td&gt;IA-64&lt;br /&gt;64비트 윈도우&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NULL 포인터&lt;br /&gt;할당&lt;/td&gt;
&lt;td&gt;0x00000000~&lt;br /&gt;0x0000FFFF&lt;/td&gt;
&lt;td&gt;0x00000000~&lt;br /&gt;0x0000FFFF&lt;/td&gt;
&lt;td&gt;0x00000000'00000000&lt;br /&gt;0x00000000'0000FFFF&lt;/td&gt;
&lt;td&gt;0x00000000'00000000&lt;br /&gt;0x00000000'0000FFFF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;유저 모드&lt;/td&gt;
&lt;td&gt;0x00010000~&lt;br /&gt;0x7FFEFFFF&lt;/td&gt;
&lt;td&gt;0x00010000~&lt;br /&gt;0xBFFEFFFF&lt;/td&gt;
&lt;td&gt;0x00000000'00010000&lt;br /&gt;0x000007FF'FFFEFFFF&lt;/td&gt;
&lt;td&gt;0x00000000'00010000&lt;br /&gt;0x000006FB'FFFEFFFF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;64KB 접근 금지&lt;/td&gt;
&lt;td&gt;0x7FFF0000~&lt;br /&gt;0x7FFFFFFF&lt;/td&gt;
&lt;td&gt;0xBFFE0000~&lt;br /&gt;0xBFFFFFFF&lt;/td&gt;
&lt;td&gt;0x000007FF'FFFF0000&lt;br /&gt;0x000007FF'FFFFFFFF&lt;/td&gt;
&lt;td&gt;0x000006FB'FFFF0000&lt;br /&gt;0x000006FB'FFFFFFFF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;커널모드&lt;/td&gt;
&lt;td&gt;0x80000000~&lt;br /&gt;0xFFFFFFFF&lt;/td&gt;
&lt;td&gt;0xC0000000~&lt;br /&gt;0xFFFFFFFF&lt;/td&gt;
&lt;td&gt;0x00000800'00000000&lt;br /&gt;0xFFFFFFFF'FFFFFFFF&lt;/td&gt;
&lt;td&gt;0x000006FC'00000000&lt;br /&gt;0xFFFFFFFF'FFFFFFFF&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;32비트 윈도우 커널과 64비트 윈도우 커널은 매우 유사한 파티션 구조을 가지고 있다. 차이점이 있다면 각 파이션의 크기와 위치가 다를뿐이다.&lt;/li&gt;
&lt;li&gt;① 널 포인터 할당 파티션
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로그래머가 NULL 포인터 할당 연산을 수행할 경우를 대비하기 위해 준비된 영역이다. 만일 프로세스의 특정 스레드가 이 파티션에 대해 읽거나 쓰기를 시도하게 되면 접근 위반(access violation)이 발생한다.&lt;/li&gt;
&lt;li&gt;Win32 애플리케이션 프로그래밍 인터페이스(API)의 함수들은 이 파티션에 대해서 예약 조차도 허용하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;② 유저 모드 파티션
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로세스의 주소 공간 내에서 활용될 수 있는 파티션이다. 프로세스는 다른 프로세스가 사용하는 유저 모드 파티션을 가리키는 포인터를 사용할 수 없으며, 이를 통한 읽기, 쓰기도 불가능하다.&lt;/li&gt;
&lt;li&gt;이 파티션은 모든 애플리케이션에 대해 프로세스가 유지해야하는 대부분의 데이터가 저장되는 영역이기도 하다. 각각의 프로세스는 데이터 저장을 위한 자신만의 파티션을 가지기 때문에 다른 프로세스의 영향으로 인해 데이터가 소실될 가능성은 거의 없으며, 이러한 특성은 시스템을 좀 더 안정적으로 동작할 수 있도록 해 준다.&lt;/li&gt;
&lt;li&gt;시스템은 이 파티션을 커널 코드, 디바이스 드라이버 콛, 장치 I/O 캐시 버퍼, 논페이지 풀 할당, 프로세스 페이지 테이블 등을 저장하기 위해 사용한다.&lt;/li&gt;
&lt;li&gt;x86 윈도우에서 더 큰 유저 모드 파티션 획득하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2GB 이상의 유저 모드 파티을 확보하려면 부트 구성 데이터를 변경한 후 시스템을 재시작하면 된다. 최대 3GB로 확장할 수 있다.&lt;/li&gt;
&lt;li&gt;하지만 커널 주소공간이 2GB 이하로 줄어들게 돼 스레드의 개수나 스택의 크기 그리고 시스템이 생성할 수 있는 다른 리소스의 개수를 이전보다 줄일 수밖에 없다. 또한 기본 설정에서는 최대 128GB 램을 설치할 수 있는 것에 반해 이 경우 64GB 램밖에 설치할 수 없게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;64비트 윈도우에서 유저 모드 파티션으로 2GB만 사용하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;32비트 애플리케이션을 64비트 환경으로 쉽게 포팅할 수 있는 방법을 많이 제공하고 있지만 단순히 애플리케이션을 다시 빌드하기만 해서는 문제가 해결되지 않고 에러가 발생할 수 있다. 이때, 시스템이 어떤 방식이로든 0x00000000'7FFFFFFF를 초과하는 메모리를 할당하지 않을 것임을 보증해 줄 수만 있다면 애플리케이션은 정상 동작할 수 있을 것이다.&lt;/li&gt;
&lt;li&gt;운영체제는 프로세스의 가용 주소 공간을 2GB이하로 제한하는 주소 공간 샌드박스(address space sandbox) 내에서 애플리케이션을 수행하는 방식을 통해 이러한 기능을 제공할 수 있다.&lt;/li&gt;
&lt;li&gt;기본적으로 64비트 애플리케이션을 구행하면 시스템은 유저 모드 주소 공간에서 사용되는 주소 값을 0x00000000'80000000 미만으로 하도록 하여 64비트 주소 공간의 하위 2GB에서만 메모리를 할당 받을 수 있도록 한다. 이것이 주소 공간 샌드박스다. 만일 64비트 애플리케이션이 전체 유저 모드 파티션을 사용하고자 한다면 반드시 /LARGEADDRESSAWARE 링커 스위치를 이용하여 링킹되어야 한다.&lt;/li&gt;
&lt;li&gt;DLL 파일에 설정된 /LARGEADDRESSAWARE 값은 무시되므로 DLL은 전체 4TB의 유저 모드 파티션에서도 정상적으로 동작하도록 작성되어야만 하며, 그렇지 않을 경우 어떤 일이 발생할지에 대해서는 정의된 바 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;③ 커널 모드 파티션
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;운영체제를 구성하는 코드들이 위치하게 된다. 스레드 스케줄링, 메모리 관리, 파일시스템 지원, 네트워크 지원 등을 구현하는 코드와 모든 디바이스 드라이버들이 이 파이션에 로드된다. 이 파티션 영역에 존재하는 내용은 모든 프로세스에 의해 공유된다.&lt;/li&gt;
&lt;li&gt;비록 이 파티션이 모든 프로세스의 유저 모드 파티션의 상위에 존재하기는 하지만, 이 파티션 내의 코드와 데이터는 완벽하게 보호된다. 만일 애플리케이션에서 이 파티션에 대해 읽거나 쓰기를 시도하게 되면, 이러한 작업을 시도한 스레드는 접근 위반을 유발한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;3. 주소 공간 내의 영역
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로세스가 생성되고 그에 따른 고유의 주소 공간이 주어지면, 대부분의 가용 주소 공간은 프리이거나 할당되지 않은 상태가 된다. 이러한 주소 공간을 사용하기 위해서는 먼저 VirtualAlloc 함수를 호출하여 영역(region)을 할당해야 한다. 이러한 작업을 영역을 예약(reserve)한다고 한다.&lt;/li&gt;
&lt;li&gt;주소 공간 상에 영역을 예약할 때에는 항상 영역의 시작 주소가 할당 단위(allocation granularity) 경계 상에 위치해야 한다. 할당 단위는 각 CPU별로 서로 상이할 수 있다. 하지만 이 책을 쓸 당시에는 모든 CPU가 64KB 할당 단위를 사용하고 있었다. 이는 각 영역의 시작 주소가 항상 64KB로 나누어 떨어지는 위치로부터 시작되어야 한다는 의미이다.&lt;/li&gt;
&lt;li&gt;주소 공간 상에 영역을 예약할 때 영역의 크기는 반드시 시스템의 페이지 크기의 배수로 설정해야한다. 페이지(page)란 운영체제가 메모리를 관리할 때 사용하는 최소 단위를 말한다. 할당 단위와 동일하게 페이지 크기는 CPU별로 서로 상이할 수 있는데, x86과 x64 시스템에서는 4KB의 페이지 크기를 사용하지만, IA-64에서는 8KB의 페이지 크기를 사용한다. 시스템은 자동적으로 사용자가 요청한 영역의 크기를 페이지 크기의 배수가 되도록 올림을 수행한다.&lt;/li&gt;
&lt;li&gt;때때로 시스템은 프로세스를 대신하여 주소 공간 상에 특별한 영역을 예약하기도 한다. 프로세스 환경블록(PEB)과 스레드 환경 블록(TEB)이 그렇다&lt;/li&gt;
&lt;li&gt;사용자가 새로운 주소 공간을 예약할 때에는 영역의 시작 주소를 할당 단위 경계(현재까지 모든 CPU에서는 64KB)로 맞출 것을 요구하지만, 운영체제 자체는 이와 같은 제한에서 자유롭다. 따라서 프로세스의 PEB나 TEB의 시작 주소는 64KB 경계로부터 시작하지 않을 수 있다. 그러나 영역의 크기는 여전히 CPU의 페이지 크기의 배수여야 하는 제한을 가지고 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;4. 물리적 저장소를 영역으로 커밋하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주소 공간의 예약된 영역을 사용하기 위해서는 반드시 물리적 저장소를 할당해야 하며, 이후 할당된 저장소와 예약된 영역을 매핑해 주어야 한다. 이러한 절차를 물리적 저장소를 커밋(commit)한다고 한다. 물리적 저장소를 예약된 영역에 커밋하기 위해서는 VirtualAlloc 함수를 한 번 더 호출해 주어야 한다.&lt;/li&gt;
&lt;li&gt;물리적 저장소를 에약된 영역에 커밋할 때 예약된 영역 전체에 대해 물리적 저장소를 커밋할 필요는 없다.&lt;/li&gt;
&lt;li&gt;프로그램이 예약된 영역에 커밋된 물리적 저장소를 더 이상 사용할 필요가 없다면 물리적 저장소를 해제해야 한다. 이러한 절차를 물리적 저장소를 디커밋(decommint)한다고 하며, VirtualFree 함수를 사용하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;5. 물리적 저장소와 페이징 파일
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예전의 운영체제에서는 물리적 자장소가 시스템에 탑재된 램의 크기를 의미하는 말로 사용되었다. 최근의 운영체제는 디스크 공간을 메모리처럼 활용할 수 있는 기능을 가지고 있다. 디스크 상에 존재하는 이러한 파일을 페이징 파일(paging file)이라고 하며, 모든 프로세스가 사용할 수 있는 가상의 메모리로 사용된다.&lt;/li&gt;
&lt;li&gt;가상의 메모리가 동작하기 위해서는 CPU 자체의 충분한 지원이 전제되어야만 한다. 스레드가 물리적 저장소에 있는 특정 바이트에 대해 접근을 시도했을 때, CPU는 접근하고자 하는 바이트가 램에 있는지 아니면 디스크에 있는지의 여부를 판단할 수 있어야 한다.&lt;/li&gt;
&lt;li&gt;애플리케이션 관점에서 보면 페이징 파일을 사용하면 애플리케이션이 사용할 수 있는 램(혹은 저장소)의 크기가 증가된 것과 같은 효과를 가져온다.&lt;/li&gt;
&lt;li&gt;운영체제는 CPU와 협력하여 페이징 파일에 애플리케이션이 필요로 하는 데이터가 있을 때 기존 램의 내용을 페이징 파일로 내보내고, 페이징 파일의 내용을 램으로 읽어 들인다. 페이징 파일을 활용하게 되면 애플리케이션을 사용할 수 있는 램의 공간이 외관상 늘어나는 것으로 보이는 것은 사실이지만, 페이징 파일을 사용할지의 여부는 여전히 사용자가 선택할 수 있다.&lt;/li&gt;
&lt;li&gt;프로세스 내의 스레드가 프로세스 주소 공간에 있는 데이터 블록에 접근을 시도하는 과정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1. 스레드가 접근하고자 하는 데이터가 램에 존재하는 경우 수항된다. 이 경우 CPU는 데이터의 가상 메모리 주소를 메모리 내의 물리적 주소로 변경한 후 데이터에 접근하게 된다.&lt;/li&gt;
&lt;li&gt;2. 스레드가 접근하려는 데이터가 램에 존재하지 않으며, 페이징 파일의 어딘가에 위치하는 경우에 수행된다. 이 경우 스레드가 이 영역에 접근을 시도하면 페이지 폴트(page fault)를 일으키게 되고 CPU는 운영체제에게 이러한 사실을 전달하게 된다. 이때 운영체제는 램에서 프리 페이지를 찾게 되는데, 만일 프리 페이지가 존재하지 않는 경우 램에 있는 페이지 중 하나를 프리 상태로 변경해야 한다. 수정된적이 없다면 단순히 프리 상태로 변경하지만 수정된 적이 있다면 먼저 선택된 페이지의 내용을 램에서 페이징 파일로 복사한 후 프리 상태로 변경한다. 이러한 작업이 끈타고 나면 앞서 스레드가 접근하고자 했던 데이터를 페이징 파일로부터 프리 페이지로 가져온다. 이제 CPU는 앞서 페이지 폴트를 유발했던 명령을 다시 수행하게 되고, 가상 메모리를 물리적 램의 주소로 변경한 후 데이터에 접근하게 된다.&lt;/li&gt;
&lt;li&gt;램에 있는 내용을 페이징 파일에 쓰거나 반대로 페이징 파일의 내용을 램으로 가져오는 작업이 많아지면 많아질수록 하드 디스크 트레쉬가생기게 되고 시스템의 수행 속도는 점점 더 느려질 것이다.(thrashing : 운영체제가 프로그램을 수행하지 못하고 대부분의 시간을 페이지 파일과 램 사이의 스와핑에 소비하는 현상) 따라서 컴퓨터에 추가적으로 램을 설치하게 되면 스레싱 정도가 감소하여 시스템의 성능이 개선된다.&lt;/li&gt;
&lt;li&gt;① 페이지 파일 내에 유지되지 않는 물리적 저장소
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제로 애플리케이션을 수행하면 시스템은 애플리케이션을 구성하는 .exe 파일을 열어서 애플리 케이션을 구성하는 코드와 데이터의 크기를 얻어낸다. 이후 프로세스의 주소 공간에 얻어낸 크기만큼 영역을 에약하고, 이 영역에 대한 커밋된 물리적 저장소를 .exe 파일 자체라고 설정한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;시스템은 페이징 파일에 공간을 할당하는 대신 프로세스의 주소공간에 예약된 영역을 이처럼 이용함으로써 .exe 파일의 내용이나 이미지 등을 사용할 수 있게 된다. 이렇게 하면 애플리케이션은 좀 더 빠르게 로딩될 수 있고, 페이징 파일의 크기를 증가시키지 않고 그대로 유지할 수 있게 된다.&lt;/li&gt;
&lt;li&gt;하드 디스크 상에 존재하는 프로그램 파일(.exe나 DLL 파일)이 주소 공간의 특정 영역에 대한 물리적 저장소로 사용되는 경우, 이러한 파일을 메모리 맵 파일이라고 부른다. .exe나 DLL에 대해 로드를 시도하면 시스템은 자동적으로 프로세스의 주소 공간에 영역을 예약하고 해당 파일을 이 영역에 매핑한다. 추가적으로, 시스템은 주소 공간의 특정 영역에 프로그램 파일이 아닌 데이터 파일을 매핑할 수 있는 방법도 제공하고 있다.&lt;/li&gt;
&lt;li&gt;윈도우는 여러 개의 페이징 파일을 사용할 수 있다. 물리적으로 서로 다른 하드 디스크 상에 다수의 페이징 파일을 구성하며 시스템을 좀 더 빨리 구동할 수 있다. 왜냐하면 물리적으로 서로 다른 드라이브에 대해서는 동시에 읽고 쓰는 것이 가능하기 때문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;8. 데이터 정렬의 중요성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 정렬의 문제는 운영체제의 메모리 구조와 관련이 있다기보다는 CPU의 구조와 관련되어 있다.&lt;/li&gt;
&lt;li&gt;CPU는 데이터가 적절하게 정렬되어 있을 때 더욱 효율적으로 접근할 수 있다. 데이터가 저장되어 있는 메모리의 주소 값을 데이터의 크기로 나누었을 때 나머지가 0인 경우 데이터가 정렬되어 있다고 한다.(Alignment)&lt;/li&gt;
&lt;li&gt;CPU가 메모리 상에 정렬되지 않은 데이터를 읽어오려 하면 두 가지 중 한가지 작업이 수행된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;첫째, 예외를 유발한다&lt;/li&gt;
&lt;li&gt;둘째, 정렬된 위치들을 여러 번 읽어서 정렬되지 않은 데이터를 모두 읽을 때까지 반복한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CPU가 메모리에 여러 번 접근하게 되면 속도에 영향을 주므로 애플리케이션이 최상의 성능을 발휘하도록 하려면 데이터가 적절히 정렬되도록 코드를 작성해야 한다.&lt;/li&gt;
&lt;li&gt;기본적으로 데이터 비정렬 폴트는 하드웨어가 직접 처리한다.&lt;/li&gt;
&lt;li&gt;컴파일러가 생성한 추가적인 코드를 통해 비정렬 문제를 해결하는 것이 CPU가 비정렬 데이터 접근 시 예외를 유발하고 그 예외를 운영체제가 처리하는 것에 비하면 훨씬 더 효율적이라 할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>프로그래밍 서적/Window via C++</category>
      <author>LEDPEAR</author>
      <guid isPermaLink="true">https://ledpear.tistory.com/119</guid>
      <comments>https://ledpear.tistory.com/119#entry119comment</comments>
      <pubDate>Sun, 7 Nov 2021 16:46:18 +0900</pubDate>
    </item>
    <item>
      <title>CHAPTER 04 : 복합 데이터형 (.5 ~</title>
      <link>https://ledpear.tistory.com/118</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;4.5 공용체
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서로 다른 데이터형을 한 번에 한 가지만 보관할 수 있는 데이터 형식&lt;/li&gt;
&lt;li&gt;여러 데이터형을 보관할 수 있는 데이터 형식이지만 한 시점에 어느 한 가지만 보관할 수 있다.&lt;/li&gt;
&lt;li&gt;공용체의 크기는 가장 큰 멤버의 크기가 된다.&lt;/li&gt;
&lt;li&gt;익명 공용체 : 이름이 없는 공용체로, 본질적으로 익명 공용체의 멤버들은 동일한 주소를 공유하는 변수들이 된다. 당연히 한 번에 한 멤버만 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;공용체는 운영 체제나 하드웨어 데이터 구조에 자주 사용되기도 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;4.6 열거체 (enum)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;열거체는 대입 연산자만 사용하도록 정의되어 있다. 특히, 산술 연산이 허용되지 않는다. 그러나 일부 C++는 이 제한을 지키지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;4.7 포인터와 메모리 해제
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주소 연산자(&amp;amp;)를 변수 앞에 붙이면 그 변수의 주소(참조값)을 알 수 있다.&lt;/li&gt;
&lt;li&gt;간접값(indirect value) 연산자 또는 간접 참조(dereferencing) 연산자라고 부르는 *를 포인터 이름 앞에 붙이면 그 주소에 저장되어 있는 값이 된다.&lt;/li&gt;
&lt;li&gt;그냥 주소값만 넣으면 되는것이 아니라 데이터형이 일치해야 한다.&lt;/li&gt;
&lt;li&gt;new를 사용한 후에는 반드시 나중에 delete를 사용해야 한다. (new[]라면 delete[])&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>프로그래밍 서적/C++ 기초 플러스</category>
      <author>LEDPEAR</author>
      <guid isPermaLink="true">https://ledpear.tistory.com/118</guid>
      <comments>https://ledpear.tistory.com/118#entry118comment</comments>
      <pubDate>Sun, 7 Nov 2021 16:18:23 +0900</pubDate>
    </item>
    <item>
      <title>Techniques - 항목 29 : 참조 카운팅 (Reference Counting)</title>
      <link>https://ledpear.tistory.com/100</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;참조 카운팅(Reference counting)이란 여러 개의 객체들이 똑같은 값을 가졌으면, 그 객체들로 하여금 그 값을 나타내는 하나의 데이터를 공유하게 해서 데이터의 양을 절약하는 기법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기법에는 두 가지 목표가 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째, 힙 객체를 둘러싼 내부 정보를 유지하는 작업을 단순하게 하자&lt;br /&gt;둘째, 똑같은 값을 가지고 있는 객체들이 그 값을 하나씩 꿰어차도록 놔두는 것은 낭비이므로 그 값을 나타내는 데이터 하나만 공유하게 하면 여러모로 이득이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참조 카운팅은 기본적으로 이렇게 구현합니다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조 카운트가 저장되는 위치는 굳이 객체 내부에 있을 필요가 없다. 실제 객체 하나에 대해서 참조 카운트는 하나만 잇으면 되기 때문에 참조 카운트와 값을 묶어서 관리해도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 참조 카운트와 값을 저장하는 클래스를 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 클래스는 오로지 기존 객체의 구현을 보조한느 것이기 때문에 private 영역에 클래스를 중첩(nesting) 시키고 클래스가 아닌 구조체로 선언하여 모든 멤버 함수가 액세스 할 수 있게 합니다.&lt;br /&gt;(이렇게 구조체를 클래스의 private 영역에 중첩시켜 두면, 이 구조체는 클래스의 모든 멤버 함수가 액세스 할 수 있고 외부에는 절대로 노출되지 않는다 (물론, 프렌드는 예외))&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 복사에 대해서는 객체를 관리할 수 있지만 객체가 이미 만들어진 값을 새로 생성하려고 할 때 기존 객체를 추적해서 참조하게 하지는 못합니다. 이 부분은 따로 작업을 해주어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 객체의 소멸자는 &lt;br /&gt;if (--value-&amp;gt;refCount == 0) delete value;&lt;br /&gt;와 같은 구문을 통해 참조 카운팅에 따라 객체를 관리하게 되고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대입했을 때 Rvalue는 참조 카운팅을 증가시켜야 하고 Lvalue는 참조 카운팅을 감소시켜야 하며 참초 카운팅이 0이 됐을 경우 delete 시켜줘야 합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기록시점 복사(Copy-on-Write)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조 카운팅 기능을 가진 문자열의 기본적인 뼈대를 마무리하는 마지막 부품은 배열-대괄호 연산자([])이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상수 객체에 대한 [] 연산자와 비상수 객체에 대한 [] 연산자 둘다 구현되는데, 상수 버전은 읽기 전용의 연산만 맡으면 되기 때문에 간단히 값을 반환하는 것으로 구현된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 비상수 객체에 대한 [] 연산자는 값이 수정될 가능성이 있으므로 참조 카운트를 1 감소시킨 후 새로운 객체를 생성하여 독립적인 객체로 만들어 줘야 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;포인터, 참조자, 그리고 기록시점 복사&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 구현된 기록시점 복사 기법은 호율과 정확성을 유지할 수 있는 꽤 괜찮은 방법이고 특히 운영체제에서 이 아이디어가 많이 사용되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 문제가 하나 남아있다. 만약 []연산 이후 객체의 참조카운트가 증가되어 1이 아니게 됐을 경우 변수로 저장해둔 []연산의 결과를 수정할 경우 참조하고 있는 모든 포인터가 가리키는 객체가 수정이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하는 방법은 최소 3 가지이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이런 문제를 무시하고, 문제가 없는 것처럼 꾸미기&lt;/li&gt;
&lt;li&gt;불법으로 규정하기&lt;/li&gt;
&lt;li&gt;이 문제를 적극적으로 직접 없애기&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째 방법의 핵심은 객체의 공유가능 여부를 표시하는 플래그를 넣는 것이다. 처음에는 플래그를 켜고, 나중에 그 객체가 나타내는 값에 대해 읽기/쓰기 겸용 operator[]가 호출될 때마다 이 플래그를 끕니다. 일단 그 플레그가 꺼져 있게 되면, 꺼진 상태를 유지합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참조 카운팅 기능을 가진 기본 클래스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조 카운팅 기능을 가진 객체의 기반 코드를 제공하는 C++ 기본 클래스를 만드는 것부터 시작합니다. 자동 참조 카운팅 기능을 이용하고자 하는 클래스는 이 기본 클래스를 상속하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 코드 생략&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참조 카운트 조작을 자동화하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 함수를 직접 호출하지 않고 재사용 가능한 클래스에다 몰아버리는 것이 더 깔끔하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포인터처럼 동작하는 객체로 포인터를 대신하면 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 객체를 가리켜 스마트 포인터라고 한다. 스마트 포인터에 대해서는 &lt;a href=&quot;https://ledpear.tistory.com/99&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;항목 28&lt;/a&gt;에서 자세히 공부할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스마트 포인터 객체는 진짜 포인터처럼 멤버 선택(-&amp;gt;)과 역참조(*) 연산을 지원하고, 타입 제약이 엄격하다는 성질(T에 대한 스마트 포인터로 T 이외의 객체를 가리키게 할 수는 없다)만 알고 있으면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스마트 포인터 기능이 들어간 템플릿은 객체 생성, 대입, 소멸이 이루어질 때 그 상황에 맞게 refCount 필드를 적절히 조작하도록 만들어져 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포인터를 가진 클래스에 대해서는 복사생성자(그리고 대입 연산자)를 직접 만들지 않으면 포인터만 복사하고 실제 값은 복사하지 않는 경우가 있으므로 주의해야 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;헤쳐 모여!&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 내용을 기반으로 참조 카운팅 클래스를 만들어 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책 내용 참고&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기존의 클래스에 참조 카운팅 기능을 부착하는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 내용은 클래스의 소스코드를 직접 건드릴 수 있다는 가정을 아래에 깔고 있었습니다. 하지만 참조 카운팅 기능을 넣었으면 하는 클래스를 직접 건드릴 수 없다면 어떻게 하시겠습니까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 배운 설계 방법을 약간만 고치면, 이미 만들어져 있는 어떤 타입에도 참조 카운팅 기능을 붙일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책 내용 참고&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참조 카운팅 기능을 구현할 때 주의할 점&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비상수 [] 연산자를 사용하는 경우 새로운 값으로 생성해야 한다.&lt;/li&gt;
&lt;li&gt;대입 연산자와 복사 연산자에서 참조 값을 변경해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;총정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조 카운팅은 무료로 얻어지지 않습니다. 참조 카운팅으로 유지되는 객체값에는 참초 횟수가 반드시 포함되며, 관련된거의 모든 동작에서 이 참조 횟수를 조작해야 합니다. 따라서 객체값에는 객체값 이외의 메모리가 추가로 들어가게 되고, 당연히 이 메모리를 액세스 하는 코드도 필요합니다. 게다가, 참조 카운팅을 쓰는 클래스는 그렇지 않은 클래스보다 더 복잡한 소스 코드로 만들어집니다. 복잡하게 설계한 이 클래스는 공유가 빈번한 경우에도 엄청난 효율 향상을 보장하고, 객체의 소유관계를 추적할 필요도 없을 뿐만 아니라, 코드의 재사용성이 매우 높습니다. 그럼에도 불구하고, 제작, 시험, 문서화, 유지보수라는 개발 과정을 고려하지 않을 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조 카운팅은 객체들 사이에서 값의 공유가 빈번하다는 가정 하에 고안한 최적화 기법입니다. 만약에 참조 카운팅을 쓰지 않은 코드보다 참조 카운팅을 쓴 코드가 메모리도 더 많이 먹고 코드도 더 많이 실행한다면, 이 가정은 실패한 것입니다. 하지만 객체들이 공통된 값을 가지는 경향이 뚜렷한 경우에는 참조 카운팅을 통해 실행 시간과 메모리 공간을 절약 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조 카운팅이 효율 향상에 효과적일 수 있는 상황은 다음의 두 가지로 정리할 수 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;상대적으로 많은 객체들이 상대적으로 적은 값을 공유할 때.&lt;br /&gt;이러한 상황은 대개 대입 연산자와 복사 생성자가 호출될 때 만들어집니다. 객체 개수에 대한 값 개수의 비율이 클수록 참조 카운팅을 할 필요가 더 커집니다.&lt;/li&gt;
&lt;li&gt;어떤 객체값을 생성하거나 소멸시키는데 많은 비용이 들거나 메모리 소모가 클 때.&lt;br /&gt;이러한 경우라고 해도, 여러 객체들이 값을 공유하지 않으면 참조 카운팅은 별 효과를 보이지 않습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞의 조건이 충족되었다고 하더라도 참조 카운팅을 사용한 코드 설계가 여전히 적합하지 않을 경우도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍 서적/More Effective C++</category>
      <author>LEDPEAR</author>
      <guid isPermaLink="true">https://ledpear.tistory.com/100</guid>
      <comments>https://ledpear.tistory.com/100#entry100comment</comments>
      <pubDate>Sat, 9 Oct 2021 18:19:10 +0900</pubDate>
    </item>
  </channel>
</rss>