<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>AlgorFati의 개발 기록</title>
    <link>https://algorfati.tistory.com/</link>
    <description>algorfati@gmail.com</description>
    <language>ko</language>
    <pubDate>Sat, 30 May 2026 17:46:05 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>AlgorFati</managingEditor>
    <image>
      <title>AlgorFati의 개발 기록</title>
      <url>https://tistory1.daumcdn.net/tistory/3944813/attach/724d39cf229447d6a954d957a10dc73f</url>
      <link>https://algorfati.tistory.com</link>
    </image>
    <item>
      <title>[Python] Python은 GIL이 있어서 레이스 컨디션으로부터 자유로운가?</title>
      <link>https://algorfati.tistory.com/234</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;서론&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Python을 사용하는 많은 개발자들이 GI&lt;/span&gt;&lt;span&gt;L(Global Inte&lt;/span&gt;&lt;span&gt;rpreter Lock&lt;/span&gt;&lt;span&gt;)에 대해 오해하고 있다.&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;특히 &quot;GIL이 있어&lt;/span&gt;&lt;span&gt;서 Pytho&lt;/span&gt;&lt;span&gt;n은 멀티스레드 환경&lt;/span&gt;&lt;span&gt;에서 레이스 컨&lt;/span&gt;&lt;span&gt;디션으&lt;/span&gt;&lt;span&gt;로부터 안전하&lt;/span&gt;&lt;span&gt;다&quot;는 생각은 매우 위험한&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;오해이다. 이 글에&lt;/span&gt;&lt;span&gt;서는 실제&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;코드를 통해 GIL이 레&lt;/span&gt;&lt;span&gt;이스 컨디션을 완전히 방&lt;/span&gt;&lt;span&gt;지하지 못한다는 것을 보여준&lt;/span&gt;&lt;span&gt;다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;GIL이&lt;/span&gt;&lt;span&gt;란 무엇인가?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;GIL은 Python&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;인터프리터가 한 번에 하나의 스&lt;/span&gt;&lt;span&gt;레드만 Python 바이트&lt;/span&gt;&lt;span&gt;코드를 실행할 수&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;있도록 제한하는 메커&lt;/span&gt;&lt;span&gt;니즘이다. 이는&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Py&lt;/span&gt;&lt;span&gt;thon의 메모리&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;관리와 C 확장 모듈의 안전성을&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;보장하기 위해&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;존재한&lt;/span&gt;&lt;span&gt;다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Race Condition은 무엇인가?&lt;/h2&gt;
&lt;p data-end=&quot;90&quot; data-start=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;Race Condition(경쟁 조건)은 &lt;b&gt;두 개 이상의 작업이 동시에 실행되면서 공유 자원에 접근할 때, 실행 순서에 따라 결과가 달라지는 상황&lt;/b&gt;을 말한다.&lt;/p&gt;
&lt;p data-end=&quot;102&quot; data-start=&quot;92&quot; data-ke-size=&quot;size16&quot;&gt;간단히 예를 들면:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;229&quot; data-start=&quot;103&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;140&quot; data-start=&quot;103&quot;&gt;두 개의 쓰레드가 같은 변수 x를 동시에 수정하려고 할 때,&lt;/li&gt;
&lt;li data-end=&quot;183&quot; data-start=&quot;141&quot;&gt;쓰레드 A는 x += 1, 쓰레드 B도 x += 1을 실행하지만,&lt;/li&gt;
&lt;li data-end=&quot;229&quot; data-start=&quot;184&quot;&gt;순서에 따라 x 값이 1만 증가하거나, 2만큼 제대로 증가할 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;269&quot; data-start=&quot;231&quot; data-ke-size=&quot;size16&quot;&gt;이런 예측 불가능한 결과가 바로 Race Condition의 문제이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;실험&lt;/span&gt;&lt;span&gt;: GIL이 레이스&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;컨디션을 완전히 방지하는&lt;/span&gt;&lt;span&gt;가?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;다음과 같은 간단한&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;코드로 실험을 진행해본다:&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744422575674&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import threading

# Shared variables
counter = 0
try_count = 1000

def increment_counter():
    global counter
    for _ in range(try_count):
        # Complex operation to increase chance of GIL switching to another thread
        temp = counter
        # I/O operation to increase chance of GIL release
        with open('temp.txt', 'a') as f:
            f.write('1')
        counter = temp + 1

def main():
    thread_num = 8
    # Create threads
    threads = []
    for _ in range(thread_num):
        thread = threading.Thread(target=increment_counter)
        threads.append(thread)
        thread.start()
    
    # Wait for all threads to complete
    for thread in threads:
        thread.join()
    
    # Print the final counter value
    print(f&quot;Final counter value: {counter}&quot;)
    print(f&quot;Expected value: {try_count * thread_num}&quot;)

if __name__ == &quot;__main__&quot;:
    main()&lt;/code&gt;&lt;/pre&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;span&gt;이 코드는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;8개의 스레드가 각각 100&lt;/span&gt;&lt;span&gt;0번씩 카운터를 증가시키는&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;작업을 수행한다.&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이론적으로는 최종&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;카운터 값이 8000(10&lt;/span&gt;&lt;span&gt;00 &amp;times; 8)이 되어야&lt;/span&gt;&lt;span&gt;&lt;span&gt; 한&lt;/span&gt;다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;결과 분석&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 코&lt;/span&gt;&lt;span&gt;드를 실행하면 다음과 같은 결과를 얻&lt;/span&gt;&lt;span&gt;을 수 있다:&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744422629886&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Final counter value: 1001
Expected value: 8000&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;왜 이런&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;현상이 발생하는&lt;/span&gt;&lt;span&gt;가?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;I/O 작업&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;중의 GIL 해제&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;파일&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;I/O 작업은&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;GIL을 해제한&lt;/span&gt;&lt;span&gt;다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이&lt;/span&gt;&lt;span&gt;는 다른 스레드가 실행될&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;수 있는&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;기회를 제공한&lt;/span&gt;&lt;span&gt;다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;읽기-수정-쓰기 패턴의 문제&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744422752804&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;   temp = counter
   with open('temp.txt', 'a') as f:
       f.write('1')
   counter = temp + 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;여러 스레드가 동시에 같은 temp 값을 읽을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;I/O 작업 중에 다른 스레드가 실행되어 같은 값을 읽고 수정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스레드 수의 영향&lt;/b&gt;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;스레드 수가 많을수록 레이스 컨디션 발생 가능성이 높아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8개의 스레드가 동시에 같은 자원에 접근하려고 시도하기 때문이다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;GIL의 한계&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;GIL은 다음&lt;/span&gt;&lt;span&gt;과 같은 경우에서 레이스 컨&lt;/span&gt;&lt;span&gt;디션을 방지하지&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;못한다:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;1. I&lt;/span&gt;&lt;span&gt;/O 작업이&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;포함된 경우&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;2. C 확&lt;/span&gt;&lt;span&gt;장 모듈이 GIL&lt;/span&gt;&lt;span&gt;을 해제하는 경우&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;3. 복잡한 연산이&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;여러 단계로 나뉘어&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;있는 경우&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;레이스 컨디션&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;방지 방법&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;Lock 사용&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744423020462&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;   lock = threading.Lock()
   with lock:
       counter += 1&lt;/code&gt;&lt;/pre&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;b&gt;&lt;span&gt;Queue 사용&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744423037975&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  from queue import Queue
   q = Queue()
   q.put(1)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;b&gt;Async / Await 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;async / await을 이용한다면 훨씬 더 우아하게 이 문제를 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/insooneelife/PythonExamples/blob/master/race_condition/async_example.py&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/insooneelife/PythonExamples/blob/master/race_condition/async_example.py&lt;/a&gt;&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;결론&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;P&lt;/span&gt;&lt;span&gt;ython의 GIL은 모&lt;/span&gt;&lt;span&gt;든 레이스 컨디션을 방지하지&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;않는다. 특&lt;/span&gt;&lt;span&gt;히 I/O 작업이&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;포함된 경우&lt;/span&gt;&lt;span&gt;나 복잡한 연산이 여러 단&lt;/span&gt;&lt;span&gt;계로 나뉘어 있는 경우에는&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;레이스 컨디션이&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;발생할 수 있&lt;/span&gt;&lt;span&gt;다. 따라서&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;멀티스레드 프로그래&lt;/span&gt;&lt;span&gt;밍을 할 때는 항상 적절&lt;/span&gt;&lt;span&gt;한 동기화 메커니즘을&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;사용해야 한다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>프로그래밍/Python</category>
      <author>AlgorFati</author>
      <guid isPermaLink="true">https://algorfati.tistory.com/234</guid>
      <comments>https://algorfati.tistory.com/234#entry234comment</comments>
      <pubDate>Sat, 12 Apr 2025 11:08:23 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] AWS 윈도우 인스턴스 국내, 해외 지역 배포 예제</title>
      <link>https://algorfati.tistory.com/233</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS를 이용하여 국내와 해외에 지역에 5개씩 인스턴스를 배포하는 방법에 대한 내용을 정리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요구사항&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 AWS 세팅의 요구사항은 다음과 같다.&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;1. 인스턴스는 윈도우 서버 인스턴스를 이용함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 인스턴스 하나에 설치 등등 환경세팅해야하는 내용이 꽤나 복잡함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 인스턴스가 실행되면 자체적으로 만든 웹 서버 프로그램을 실행시켜야함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 국내, 해외 리전으로 각각 5개의 인스턴스를 띄워야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 각 인스턴스의 IP는 고정되어야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 허용해야하는 트래픽 프로토콜과 Port, IP 대역이 정해져있음&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AWS 세팅 계획&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 요구사항을 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;윈도우 서버 인스턴스는 EC2 에서 인스턴스 생성을 할 때, 윈도우 서버를 선택하면 된다.&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;인스턴스 실행 시, 프로그램이 실행되도록 하기 위해 윈도우 Task Scheduler를 이용한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 인스턴스의 IP를 고정하기 위해 Elastic IP를 이용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트래픽 허용 방법을 정의하기 위해 보안 그룹을 이용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;국내와 해외 리전에 각각 5개의 인스턴스를 띄워야 하는데, 이를 위해 먼저 리전을 국내(seoul)로 세팅하여 하나의 AMI(이미지)를 미리 만든다. 이 AMI를 이용하여 5개 서울 인스턴스를 복제한다. 해외 리전을 위한 AMI는 또 따로 생성해야하는데 만들어둔 국내 AMI를 복사하여 해외 리전 AMI를 생성하고, 기타 리소스들을 세팅하여 해외 인스턴스를 5개 생성하는 방향으로 진행하면 될 것이다.&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AWS 세팅&lt;/h2&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;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;윈도우 인스턴스 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS 웹 콘솔에서 EC2 페이지로 이동하여 오른쪽 위 지역을 서울로 세팅한 후 인스턴스를 시작 버튼을 누른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Launch an instance 페이지에서 원하는 인스턴스의 스펙을 지정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 운영체제를 윈도우 서버로 지정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스턴스 유형은 머신의 성능에 맞는 프리셋들을 의미한다. 여기에서 적절한 유형을 하나 선택한다. (t3.xlarge)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스토리지 구성에서 필요한 디스크 사이즈를 할당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키 페어는 해당 인스턴스에 접근하기 위한 용도로 사용된다. 이미 생성된 키 페어가 있다면 사용하고, 없다면 새로 하나 생성한다. 키 페어 이름을 지정하고, 유형은 RSA로 파일은 .pem로 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안 그룹은 해당 인스턴스로의 네트워크 트래픽을 관리하기 위한 용도로 사용된다. EC2 &amp;gt; 네트워크 및 보안 &amp;gt; 보안 그룹 탭으로 이동하여 용도에 맞게 이름을 지정하여 생성한다. 보안 그룹 세팅은 인스턴스 생성 이후에도 수정이 가능하기 때문에 간단하게만 세팅하고 인스턴스에서 생성한 보안그룹을 지정한다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;윈도우 인스턴스 환경세팅, 시작 프로그램 세팅&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 인스턴스에 RDP를 통해 접속한다. RDP 접속을 하기 위해 키페어 생성 때 받은 .pem 파일이 필요하다. 이 파일을 해독하여 비밀번호를 생성하고, 그 비밀번호를 입력하면 원격접속이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원격접속 후 윈도우 인스턴스의 환경세팅을 시작한다. 윈도우 서버는 기본적으로 대부분의 포트가 막혀있기 때문에 방화벽을 통해 사용할 포트를 열어주어야 한다. 방화벽 &amp;amp; 네트워크 보안 으로 이동하여 고급 세팅에서 인바운드 규칙으로 이동한다. 인바운드 규칙에서 TCP 80(HTTP), TCP 443(HTTPS)과 개발목적으로 사용될 포트 TCP(5000), UDP(9000)을 열어준다. 그 이후 개발을 위한 환경세팅을 진행한다. 파이썬 설치, 라이브러리 설치, 기타 프로그램들을 설치한다.&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;첫번째로 윈도우의 작업 스케줄러를 통해 지정한 프로그램이 윈도우 시작 시 실행되도록 세팅하는 것이다. 이렇게 하면 프로그램은 백그라운드로 실행되고 따로 로그인을 하지 않아도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째로 AutoLogon을 설치하여 자동으로 로그인되게 하고, 실행창에 shell:startup 를 입력하여 시작 프로그램 디렉터리를 띄우고 시작시킬 프로그램들의 바로가기를 넣어서 프로그램들이 실행되도록 할 수 있다. 이 방법은 프로그램들이 창을 띄우는 형태로 뜨기 때문에 좀 더 직관적이지만, 로그인을 해야만 가능하다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; 국내, 해외 리전으로 각각 5개의 인스턴스를 띄워야 함 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 단계에서 필요한 인스턴스의 프로토타입을 만들었다. 이제 이 인스턴스를 이미지 형태로 만들어야한다. AWS에서는 AMI라는 인스턴스 이미지 기능을 제공한다. 인스턴스 창으로 이동하여 인스턴스 상태 &amp;gt; 이미지 및 템플릿 &amp;gt; 이미지 생성을 클릭한다. 이름을 정한 후 이미지를 생성한다. 이제 AMI 창에서 생성된 이미지를 확인할 수 있다. AMI이 생성되는 과정에서 스냅샷도 같이 생성되는데, 스냅샷 생성은 꽤 오래 걸린다. 또한 스냅샷이 완전히 생성되기 전까지는 AMI를 이용할 수 없으니 스냅샷 창에서 진행률을 확인하고 다음 작업을 수행하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스냅샷까지 생성됨을 확인한 후, AMI로 인스턴스를 시작시킨다. 여기에서 인스턴스 유형과 키페어, 보안 그룹을 다시 설정할 수 있는데 이 예제에서는 기존 인스턴스와 동일하게 진행한다. 이제 인스턴스 개수를 5로 지정하고 인스턴스 시작을 눌러 5개의 인스턴스를 생성한다. 이제 서울 리전에 5개의 동일한 인스턴스를 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 해외 리전에 동일한 5개 인스턴스를 실행시켜야 한다. 먼저 서울 리전에 생성된 AMI를 선택한다. 작업 &amp;gt; AMI 복사를 선택하여 AMI 복사 페이지로 이동한다. 여기에서 리전을 오하이오로 변경하여 AMI를 복사한다. 이제 서울 리전에 생성된 AMI와 동일한 AMI가 미국 리전에도 생성되었다. 오른쪽 위 지역을 오하이오로 변경하고 AMI 페이지로 이동하여 방금 생성된 AMI를 선택한다. 서울에서 5개 인스턴스를 띄운것과 동일하게 오하이오에 5개 인스턴스를 띄우면 되는데, 보안그룹과 키페어는 다른 지역의 것을 이용할 수 없기 때문에 새로 생성해주어야한다. 서울 지역에서 생성한 것과 동일하게 키페어, 보안그룹을 생성하고 오하이오에도 5개 인스턴스를 띄운다.&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;각 인스턴스의 IP를 Public 고정 IP로 세팅&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스턴스에 IP를 세팅하기 위해 탄력적 IP 기능을 이용한다. 탄력적 IP 주소 할당을 통해 새 IP를 할당한다. 유형을 퍼블릭 IP로 한다. 서울, 오하이오에 각각 5개씩 할당한다. 탄력적 IP는 할당 이후 인스턴스에 연결해주어야 한다. 하나하나 클릭하여 인스턴스를 선택하고 연결해준다. 이 때, 이 탄력적 IP 주소를 재연결하도록 허용을 체크하여 인스턴스의 IP가 계속 이 IP로 잡히도록 하면 고정 IP 세팅이 완료된다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;허용 트래픽 프로토콜과 Port, IP 대역 세팅&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퍼블릭하게 띄워진 인스턴스에는 네트워크를 통한 악의적인 접근들이 들어올 수 있다. 보통 아무 제한 없이 80번 포트를 열어둔 경우 브루트 포스 IP로 통신을 날리는 봇들의 타겟이 된다. 그래서 가끔 알 수 없는 IP로부터 통신요청이 들어오는데 이는 개발자가 띄우려는 서비스의 안정성을 해칠 수 있다. 이를 방지하기 위해 AWS에서는 가상 방화벽을 위한 보안 그룹 기능을 제공한다. 보안 그룹은 인바운드(들어오는 트래픽)와 아웃바운드(나가는 트래픽)에 제한을 거는 기능들을 제공한다. 보안 그룹을 생성할 때 기본적으로 잡히는 포트는 보통 TCP 80(HTTP), TCP 443(HTTPS), TCP 3389(RDP) 등이다. 워낙 자주 사용되는 포트이고, RDP의 경우는 인스턴스에 개발자가 접속해야하기 때문에 보통 디폴트값으로 세팅된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 생성해둔 보안 그룹으로 들어간다. 인바운드 규칙 편집을 통해 규칙을 추가, 제거할 수 있다. 디폴트로 세팅된 인바운드 규칙 외에 자체적으로 만든 프로그램의 포트를 규칙에 추가해주어야한다. 예를 들어 직접 제작한 웹 서버가 5000 포트로 호스팅중이라 하면 TCP 5000을 추가해야하고, 게임 서버가 UDP 9000으로 게임 클라이언트와 통신한다면 UDP 9000을 추가해주면 된다.&lt;br /&gt;이렇게 세팅해두면 80, 443, 3389, 5000, 9000 외의 다른 포트로는 이 인스턴스에 접근할 수 없다. 여기에서 각 포트에 대해 접근할 수 있는 IP 대역도 제한할 수 있다. 보통 회사에서만 접근가능한 인스턴스를 만들거나, 협업하는 업체에서 접근 가능하도록 하는 경우 인바운드 규칙으로 IP 대역을 제한한다. 인바운드 규칙에서 소스를 설정하여 이 작업을 수행할 수 있다. 소스는 다음과 같은 규칙으로 IP 대역을 표현한다.&lt;/p&gt;
&lt;pre id=&quot;code_1734519455740&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/32는 단일 IP 주소를 나타낸다.
203.0.113.25/32

/24는 192.168.1.0부터 192.168.1.255까지의 IP 범위를 포함한다.
192.168.1.0/24

다음과 같이 전 세계 모든 IPv4 주소에서 인스턴스에 접근을 허용할 수 있다.
0.0.0.0/0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Back End</category>
      <author>AlgorFati</author>
      <guid isPermaLink="true">https://algorfati.tistory.com/233</guid>
      <comments>https://algorfati.tistory.com/233#entry233comment</comments>
      <pubDate>Mon, 16 Dec 2024 23:57:51 +0900</pubDate>
    </item>
    <item>
      <title>[Python] PyInstaller, PyArmor를 이용한 파이썬 패키징</title>
      <link>https://algorfati.tistory.com/231</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬으로 개발한 프로그램을 외부로 배포해야할 수 있다. 외부의 환경에서는 프로그램이 사용하는 모듈이나 라이브러리가 없을 수 있고, 파이썬 자체가 설치되어 있지 않을 수도 있다. PyInstaller를 이용하면 프로그램에서 이용하는 모든 것을 패키징할 수 있다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 PyInstaller 매뉴얼이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pyinstaller.org/en/v4.2/index.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pyinstaller.org/en/v4.2/index.html&lt;/a&gt;&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;먼저 PyInstaller는 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1723695910285&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install pyinstaller&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;pre id=&quot;code_1723696047529&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pyinstaller --onefile main.py&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;PyInstaller는 지정된 파이썬 파일로부터 재귀적으로 종속적인 모듈들을 모두 수집하여 바이너리파일로 패키징한다. 다음과 같은 파일들이 생성된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;194&quot; data-origin-height=&quot;111&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l3iSZ/btsI4asv0VH/lIqpDaURRR5AD7z6ZNy4M0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l3iSZ/btsI4asv0VH/lIqpDaURRR5AD7z6ZNy4M0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l3iSZ/btsI4asv0VH/lIqpDaURRR5AD7z6ZNy4M0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl3iSZ%2FbtsI4asv0VH%2FlIqpDaURRR5AD7z6ZNy4M0%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;194&quot; height=&quot;111&quot; data-origin-width=&quot;194&quot; data-origin-height=&quot;111&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;build 파일은 PyInstaller의 빌드를 위한 임시 파일이다. 다시 빌드할 때, 이 파일이 있으면 빨라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spec 파일은 빌드를 커스텀하기 위한 파일이다. (포함시킬 모듈 지정 등)&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;p data-ke-size=&quot;size16&quot;&gt;실행파일은 dist 디렉터리 내부에 생성된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;92&quot; data-origin-height=&quot;32&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGUZcM/btsI6d8Q9YY/RrwSb1r9TUYPZTSjnozllk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGUZcM/btsI6d8Q9YY/RrwSb1r9TUYPZTSjnozllk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGUZcM/btsI6d8Q9YY/RrwSb1r9TUYPZTSjnozllk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGUZcM%2FbtsI6d8Q9YY%2FRrwSb1r9TUYPZTSjnozllk%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;92&quot; height=&quot;32&quot; data-origin-width=&quot;92&quot; data-origin-height=&quot;32&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;PyArmor를 이용한 암호화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PyInstaller를 이용하여 패키징하는 경우 코드에 대한 암호화 작업은 따로 해주지 않기 때문에, 코드가 유출될 수 있다. 그러므로 패키지를 암호화해주어야 하는데 이를 위해 PyArmor를 이용하면 된다.&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;다음은 PyArmor 매뉴얼이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pyarmor.readthedocs.io/en/stable/tutorial/obfuscation.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pyarmor.readthedocs.io/en/stable/tutorial/obfuscation.html&lt;/a&gt;&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;pyinstaller는 설치되어있다고 가정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pyarmor를 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1723698635753&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install pyarmor&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 명령어를 통해 암호화된 패키징을 만들 수 있다. (pyarmor가 내부적으로 pyinstaller를 이용함)&lt;/p&gt;
&lt;pre id=&quot;code_1723698691590&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pyarmor gen --pack onefile foo.py&lt;/code&gt;&lt;/pre&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;pre id=&quot;code_1723698731762&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dist/foo&lt;/code&gt;&lt;/pre&gt;</description>
      <category>프로그래밍/Python</category>
      <author>AlgorFati</author>
      <guid isPermaLink="true">https://algorfati.tistory.com/231</guid>
      <comments>https://algorfati.tistory.com/231#entry231comment</comments>
      <pubDate>Thu, 15 Aug 2024 13:29:12 +0900</pubDate>
    </item>
    <item>
      <title>[Python] literal_eval을 이용한 외부 python 파일 읽기</title>
      <link>https://algorfati.tistory.com/230</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬 파일에는 다양한 변수들을 쉽고 편하게 저장할 수 있다. 이는 파이썬 인터프리터가 구문분석을 통해 파일을 구조화하기 때문이다. 그래서 파이썬으로 개발을 하다 보면 컨피그 파일을 .py 파일로 구성하여 사용하곤 한다. 파이썬 파일로 구성된 컨피그는 dict와 같은 자료구조로 변수들을 쉽고 편하게 저장해둘 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 문제는 이러한 컨피그 파일이 프로젝트의 포함된 코드가 아니라 프로젝트 외부로부터 입력받아야하는 상황에서 발생한다. 예를 들어 어떤 개발자가 파이썬으로 만든 실행파일을 배포한다고 했을 때, 컨피그파일이 이미 프로젝트의 일부로 들어가 있다면 실행파일에 포함되어버릴 것이다. 이 문제를 해결하기 위해 literal_eval을 이용하면 컨피그를 외부로부터 입력받고, 파이썬 구문분석을 통해 이전에 이용하던대로 사용할 수 있다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 파이썬으로 구성된 컨피그 파일이다. 파이썬의 구문분석이 동작하기 때문에 json 파일을 dict의 형태로 작성할 수 있다. 이는 컨피그 작성에 굉장히 편리하다.&lt;/p&gt;
&lt;pre id=&quot;code_1723689812194&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;settings = {
    &quot;database&quot;: {
        &quot;host&quot;: &quot;localhost&quot;,
        &quot;port&quot;: 5432,
        &quot;user&quot;: &quot;admin&quot;,
        &quot;password&quot;: &quot;secret&quot;
    },
    &quot;features&quot;: {
        &quot;enable_logging&quot;: True,
        &quot;debug_mode&quot;: False
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;이 파일은 원래 다음과 같은 형태로 참조되어 사용될 것이다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1723689967252&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import config
print(config.settings)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 코드는 import를 이용하는데, 이는 config.py라는 파일이 이미 프로젝트에 포함되어 있기에 가능한 것이다.&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;pre id=&quot;code_1723690081261&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import ast

# load the content of the config file as a string
with open(&quot;config.py&quot;, &quot;r&quot;) as file:
    config_content = file.read()

# extract the dictionary part using literal_eval
config_dict = ast.literal_eval(config_content.split('=', 1)[1].strip())


# now you can access the configuration dictionary
print(config_dict)
print(&quot;Database host:&quot;, config_dict[&quot;database&quot;][&quot;host&quot;])
print(&quot;Logging enabled:&quot;, config_dict[&quot;features&quot;][&quot;enable_logging&quot;])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서는 literal_eval을 통해 config.py 파일을 읽은 문자열에 대해 파이썬 구문분석을 수행한다. 그 이후 파이썬에서 사용 가능한 변수의 형태로 바꾸어준다. 이 방법을 이용한다면 파이썬 파일로 구성된 컨피그를 외부에서 넣어줄 수 있다.&lt;/p&gt;</description>
      <category>프로그래밍/Python</category>
      <author>AlgorFati</author>
      <guid isPermaLink="true">https://algorfati.tistory.com/230</guid>
      <comments>https://algorfati.tistory.com/230#entry230comment</comments>
      <pubDate>Thu, 15 Aug 2024 11:49:14 +0900</pubDate>
    </item>
    <item>
      <title>[Algorithm] 재귀 함수를 스택과 반복문으로 구현하기</title>
      <link>https://algorfati.tistory.com/229</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;구현 - Tail Recursion&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 상대적으로 쉬운 Tail Recursion을 스택으로 변환해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제로 사용될 알고리즘은 이진 탐색이다.&lt;/p&gt;
&lt;pre id=&quot;code_1723121563659&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int binarySearch(const std::vector&amp;lt;int&amp;gt;&amp;amp; arr, int target) 
{
    int left = 0;
    int right = arr.size() - 1;

    while (left &amp;lt;= right) 
    {
        int mid = left + (right - left) / 2;
        if (arr[mid] == target) 
            return mid;
        else if (arr[mid] &amp;gt; target) 
            right = mid - 1;
        else 
            left = mid + 1;
    }

    return -1;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;pre id=&quot;code_1723121678970&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int binarySearch(const std::vector&amp;lt;int&amp;gt;&amp;amp; arr, int target) 
{
    std::stack&amp;lt;std::pair&amp;lt;int, int&amp;gt;&amp;gt; stack;
    stack.push({0, arr.size() - 1});

    while (!stack.empty()) {
        int left = stack.top().first;
        int right = stack.top().second;
        stack.pop();

        if (left &amp;lt;= right) 
        {
            int mid = left + (right - left) / 2;

            if (arr[mid] == target) 
            {
                return mid;
            }
            if (arr[mid] &amp;gt; target) 
            {
                stack.push({left, mid - 1});
            }
            else 
            {
                stack.push({mid + 1, right});
            }
        }
    }

    return -1;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;구현 - General Recursion&lt;/b&gt;&lt;/h2&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;재귀함수를 스택으로 구현하기 위해서는 재귀함수 내부의 모든 실행 블럭들을 구분지어야한다. &lt;br /&gt;디버깅이 용이하도록 각 영역에서 값을 출력하도록 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1723121941077&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void F1(int n)
{
	std::cout &amp;lt;&amp;lt; n &amp;lt;&amp;lt; &quot; &quot; &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; std::endl;
	if (n == 0)
	{
		std::cout &amp;lt;&amp;lt; n &amp;lt;&amp;lt; &quot; &quot; &amp;lt;&amp;lt; 2 &amp;lt;&amp;lt; std::endl;
		return;
	}

	std::cout &amp;lt;&amp;lt; n &amp;lt;&amp;lt; &quot; &quot; &amp;lt;&amp;lt; 3 &amp;lt;&amp;lt; std::endl;

	for (int i = 0; i &amp;lt; n; ++i)
	{
		std::cout &amp;lt;&amp;lt; n &amp;lt;&amp;lt; &quot; &quot; &amp;lt;&amp;lt; 4 &amp;lt;&amp;lt; std::endl;
		F1(n - 1);
		std::cout &amp;lt;&amp;lt; n &amp;lt;&amp;lt; &quot; &quot; &amp;lt;&amp;lt; 5 &amp;lt;&amp;lt; std::endl;
	}

	std::cout &amp;lt;&amp;lt; n &amp;lt;&amp;lt; &quot; &quot; &amp;lt;&amp;lt; 6 &amp;lt;&amp;lt; std::endl;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수의 특성을 생각해보자. 함수는 호출될 때, 자신의 상태를 저장하기 위한 고유 스택을 생성한다. 스택에 값이 저장되어 있기 때문에, 함수 내에서 다른 함수를 호출하여 프레임을 벗어나더라도, return을 통해 해당 위치로 돌아오면 그 값을 꺼내어 실행을 재개할 수 있다. 물론 함수의 스택 프레임을 잡는 것은 운영체제가 알아서 해주지만, 여기에서는 직접 자료구조 스택을 이용하여 해본다.&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;상태를 저장하는 기능은 스택을 이용한다면 쉽게 구현할 수 있다. 어떤 작업을 실행중에 함수호출이 발생하면, 현재까지 작업중이던 내용을 스택 프레임에 저장하고 함수호출이 끝날 때 &quot;&lt;span style=&quot;color: #ee2323;&quot;&gt;해당 위치부터 실행을 재개하면 된다&lt;/span&gt;&quot;. 하지만 어떻게 해당 위치부터 작업을 재개할 수 있을까? 이를 표현하기 위해 실행 흐름의 각 구간을 나누고, 상태값을 스택 프레임에 추가적으로 넣어서 흐름을 컨트롤한다. 즉, 스택 프레임을 통해 상태를 재구성하고, phase 변수와 loop를 이용하여 흐름을 컨트롤한다. 흐름 컨트롤을 구현하기&amp;nbsp;위해 phase라는 변수와 switch case문을 이용할 것이다. 스택이 최종적으로 빌 때까지 위 과정을 반복해야 하므로 while loop으로 전체 작업을 반복한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스택 프레임에 들어갈 변수도 정해야한다. 일반적으로 다음과 같이 정의한다.&lt;/p&gt;
&lt;pre id=&quot;code_1723123104947&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;재귀함수의 모든 파라메터 + 재귀함수의 모든 로컬 변수 + phase 변수&lt;/code&gt;&lt;/pre&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;pre id=&quot;code_1723122991735&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void F2(int n)
{
	// 재귀함수 파라메터 N, phase, 재귀함수 로컬변수 i
	std::stack&amp;lt;std::tuple&amp;lt;int, int, int&amp;gt;&amp;gt; stk;

	// 시작 상태를 넣어 반복문을 실행시킨다.
	stk.push(std::make_tuple(n, 0, 0));

	while (!stk.empty())
	{
		// 스택 프레임의 레퍼런스를 참조하여 보다 쉽게 흐름 컨트롤을 수행할 수 있다.
		std::tuple&amp;lt;int, int, int&amp;gt;&amp;amp; t = stk.top();
		int current_n = std::get&amp;lt;0&amp;gt;(t);

		// phase와 i는 스택 프레임 속에 존재하면서도 값이 바뀌어 반복문의 흐름을 컨트롤하는 역할을 수행한다.
		int&amp;amp; phase = std::get&amp;lt;1&amp;gt;(t);
		int&amp;amp; i = std::get&amp;lt;2&amp;gt;(t);

		// phase를 통해 재귀함수 내 실행 흐름을 구간 단위로 컨트롤한다.
		switch (phase)
		{
		case 0:
			// 재귀함수 시작 시 수행하는 작업을 phase 0에서 수행한다.
			std::cout &amp;lt;&amp;lt; current_n &amp;lt;&amp;lt; &quot; &quot; &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; std::endl;
			if (current_n == 0)
			{
				// 재귀함수의 끝 조건 시 수행할 작업, 재귀함수의 끝을 표현하기 위해 stack에서 원소를 제거한다.
				std::cout &amp;lt;&amp;lt; current_n &amp;lt;&amp;lt; &quot; &quot; &amp;lt;&amp;lt; 2 &amp;lt;&amp;lt; std::endl;
				stk.pop();
				continue;
			}

			std::cout &amp;lt;&amp;lt; current_n &amp;lt;&amp;lt; &quot; &quot; &amp;lt;&amp;lt; 3 &amp;lt;&amp;lt; std::endl;

			// 다음 실행 흐름은 for loop로 들어가기 때문에 구간이 분리되어 있다.
			// phase를 1로 변경하면 스택 프레임(튜플의 두번째 원소) 값이 변경되고,
			// 값이 변경된 채로 while loop를 통해 1번 흐름으로 건너갈 수 있다.
			phase = 1;
			break;

		case 1:
			// 반복문에 진입 시 수행할 작업을 수행한다.
			std::cout &amp;lt;&amp;lt; current_n &amp;lt;&amp;lt; &quot; &quot; &amp;lt;&amp;lt; 4 &amp;lt;&amp;lt; std::endl;

			// 반복문 내 함수를 재귀적으로 호출하는데, 이를 표현하기 위해 새로운 스택 프레임을 생성한다.
			stk.push(std::make_tuple(current_n - 1, 0, 0));

			// 현재 스택 프레임의 phase를 2로 세팅해둔다. 
			// while loop을 다시 만나면 위에서 추가된 새로운 스택 프레임이 세팅되며,
			// 이는 현재까지의 작업을 재귀적으로 표현한다.
			// 상위의 모든 작업들이 끝난 후 stack pop이 수행되면 다시 현재 스택 프레임을 만날 것이고,
			// 그러면 phase 2를 통해 2번 흐름으로 건너간다.
			phase = 2;

			break;

		case 2:
			// 2번 흐름은 반복문 내 재귀 이후 작업을 표현한다.
			std::cout &amp;lt;&amp;lt; current_n &amp;lt;&amp;lt; &quot; &quot; &amp;lt;&amp;lt; 5 &amp;lt;&amp;lt; std::endl;
			i++;

			// i의 값에 따라 실행 흐름을 어디로 보낼 지 결정한다.
			if (i &amp;lt; current_n)
			{
				phase = 1;
			}
			else
			{
				phase = 3;
			}
			break;

		case 3:
			// 현재 프레임에서의 작업은 모두 끝났으므로, 현재 프레임을 스택에서 제거한다.
			std::cout &amp;lt;&amp;lt; current_n &amp;lt;&amp;lt; &quot; &quot; &amp;lt;&amp;lt; 6 &amp;lt;&amp;lt; std::endl;
			stk.pop(); // remove the current frame
			break;
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;p.s&lt;/b&gt;&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;</description>
      <category>프로그래밍/알고리즘</category>
      <author>AlgorFati</author>
      <guid isPermaLink="true">https://algorfati.tistory.com/229</guid>
      <comments>https://algorfati.tistory.com/229#entry229comment</comments>
      <pubDate>Thu, 8 Aug 2024 22:39:56 +0900</pubDate>
    </item>
    <item>
      <title>[Python] Python의 모듈 참조</title>
      <link>https://algorfati.tistory.com/226</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;소개&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모듈 다루기는 파이썬 프로그래밍에서 필수적이다. 모듈은 파이썬 정의와 명령어를 담은 파일이다. 어떤 파이썬 파일(.py)이라도 모듈이 될 수 있다. 개발을 하는 과정에서는, 때때로 다른 디렉토리에서 모듈을 가져와야 할 필요가 있다. 이는 코드를 여러 디렉토리에 조직적으로 관리해야 하는 큰 프로젝트에서 유용하다. 이 섹션에서는 파이썬에서 다른 디렉토리의 모듈을 어떻게 가져오는지 알아보는데, 시스템 경로를 수정하거나 상대 경로를 사용하는 방법을 포함해 여러 방법을 다룬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;모듈 임포트 방법&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬 프로젝트를 작업할 때, 종종 다른 모듈이나 패키지의 코드를 사용해야 할 수 있다. 모듈의 코드를 사용하려면 현재 파이썬 스크립트에 그 모듈을 가져와야 한다. 파이썬에는 스크립트에 모듈을 가져올 수 있게 해주는 import라는 내장 함수가 있다. 모듈을 가져오려면 import 키워드 뒤에 모듈 이름을 적으면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1716984356094&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import my_module&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;이렇게 하면 my_module 모듈이 스크립트로 가져와져서 그 안의 모든 함수와 변수를 사용할 수 있게 된다. 하지만 가끔은 원하는 모듈이 현재 스크립트와 다른 디렉토리에 위치할 수 있다. 이 경우, 모듈의 경로를 절대 경로나 상대 경로로 지정해야 한다. 절대 경로는 파일 시스템상에서 모듈의 전체 경로를 지정한다. 예를 들면 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1716984477183&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import sys
sys.path.append('/path/to/other/directory')
import my_module&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 my_module이 포함된 디렉토리가 Python이 모듈을 검색하는 디렉토리 목록에 추가된다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상대 경로는 현재 스크립트에 대한 모듈의 경로를 지정한다. 예를 들어:&lt;/p&gt;
&lt;pre id=&quot;code_1716984612606&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from ..other_directory import my_module&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 my_module을 현재 디렉토리보다 한 단계 위 디렉토리에서 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요약하자면, 파이썬에서 모듈을 가져오는 것은 import 키워드 다음에 모듈 이름을 사용하여 수행된다. 모듈이 현재 스크립트와 다른 디렉토리에 위치한 경우, 절대 경로나 상대 경로를 사용하여 위치를 지정할 수 있다.&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;pre id=&quot;code_1716984786781&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from my_module import my_function&lt;/code&gt;&lt;/pre&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;pre id=&quot;code_1716984826629&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import my_module as mm&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;다른 디렉토리의 모듈 임포트 방법&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;sys.path.append()&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬의 import 문은 프로젝트의 다른 모듈이나 파일에서 코드를 사용할 수 있게 해준다. 하지만 가끔 현재 작업 디렉토리와 다른 디렉토리에 저장된 모듈을 가져와야 할 때가 있다. 이런 경우에는 sys.path.append() 메소드를 사용해 해당 모듈이 있는 디렉토리 경로를 파이썬의 검색 경로에 추가할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 sys.path.append()를 사용해 다른 디렉토리에서 모듈을 가져오는 예제이다:&lt;/p&gt;
&lt;pre id=&quot;code_1716986573026&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import sys
sys.path.append('/path/to/directory')

import my_module&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예제에서는 먼저 sys 모듈을 가져온 다음 path.append() 메소드를 사용하여 우리 모듈이 포함된 디렉토리의 경로를 파이썬의 검색 경로에 추가한다. 그 후에는 평소와 같이 모듈 이름을 사용하여 모듈을 가져올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sys.path.append()는 지정된 디렉토리를 파이썬의 검색 경로 맨 끝에 추가한다는 점을 주목할 필요가 있다. 이는 동일한 이름의 모듈이 여러 디렉토리에 있을 경우, 파이썬은 검색 경로에서 처음 나타나는 디렉토리의 모듈을 사용한다는 의미다. 따라서 파이썬의 검색 경로에 디렉토리를 추가하는 순서를 신중하게 고려해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 sys.path.append()를 사용하면 코드의 이식성이 떨어질 수 있으므로, 가능한 한 코드 내에 파일 경로를 하드 코딩하는 것을 피하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전반적으로 sys.path.append()를 사용하여 다른 디렉토리에서 모듈을 가져오는 것은 여러 모듈과 파일이 있는 큰 프로젝트를 작업할 때 유용한 기술일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;PYTHONPATH Environment Variable&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가끔 메인 파이썬 스크립트가 위치한 디렉토리와 다른 디렉토리에 있는 모듈을 가져와야 할 수 있다. 이런 경우 PYTHONPATH 환경 변수를 사용하여 모듈을 가져올 디렉토리 경로를 지정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PYTHONPATH 환경 변수는 모듈을 가져올 때 파이썬이 검색하는 디렉토리 목록이다. 모듈을 가져올 때 파이썬은 먼저 현재 디렉토리를 검색한 다음, PYTHONPATH 변수에 나열된 모든 디렉토리를 검색한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 파이썬 스크립트 내에서 PYTHONPATH 환경 변수를 설정하는 예제이다:&lt;/p&gt;
&lt;pre id=&quot;code_1716986736302&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import os
import sys

# Add the directory containing your module to the Python path (before importing it)
module_dir = os.path.abspath('/path/to/module_directory')
sys.path.insert(0, module_dir)

# Now you can import your module as usual
import my_module&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예제에서는 먼저 나중에 필요한 os와 sys 모듈을 가져온다. 그 다음, 우리 모듈이 있는 디렉토리의 절대 경로를 정의하고 sys.path.insert()를 사용해 sys.path 목록의 맨 앞에 추가한다. 마지막으로, 평소처럼 import my_module을 사용하여 모듈을 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모듈 디렉토리를 sys.path의 맨 앞에 추가하는 것은 중요하다. 이는 파이썬이 sys.path의 위치에 따라 우선 순위를 두고 모듈을 검색하기 때문이다. 우리 모듈 디렉토리를 위치 0에 추가함으로써, 다른 디렉토리보다 높은 우선 순위를 부여한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PYTHONPATH 환경 변수를 사용하는 것은 다른 디렉토리에서 모듈을 가져오는 편리한 방법이다, 특히 여러 하위 디렉토리가 있는 큰 프로젝트에서 작업할 때 유용하다. 하지만 sys.path를 직접 수정하는 것은 의도치 않은 결과를 초래하고 디버깅하기 어려운 문제를 일으킬 수 있으므로, 가상 환경이나 pipenv, poetry와 같은 패키지 관리자를 사용하여 의존성을 관리하고 잠재적인 충돌을 피하는 것이 권장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Relative Import&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬 프로젝트를 진행하다 보면, 여러 디렉토리에 다양한 모듈이 포함될 수 있다. 때로는 현재 스크립트가 위치한 디렉토리가 아닌 다른 디렉토리에서 모듈을 가져와야 할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 상대 경로를 사용하는 import 문을 사용할 수 있다. 상대 경로 import 문은 현재 스크립트 위치에 대한 모듈의 위치를 지정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, directory1과 directory2라는 두 디렉토리가 있다고 가정하고, directory1에서 module1.py 모듈을 directory2에 위치한 스크립트로 가져오려면 다음과 같은 상대 경로 import 문을 사용할 수 있다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1716986890264&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from ..directory1 import module1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예제에서 이중 점(..)은 directory1에 접근하기 전에 디렉토리 계층에서 한 단계 위로 올라가고 싶다는 것을 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상대 경로 가져오기는 패키지 내에서만 작동한다는 점을 알아두는 것이 중요하다. 패키지는 __init__.py라는 추가 파일이 포함된 디렉토리를 의미한다. 이 __init__.py 파일은 비어 있거나 패키지의 초기화 코드를 포함할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;모듈과 패키지를 구성하는 방법&lt;/b&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;pre id=&quot;code_1716987361385&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mypackage
├── main.py
├── __init__.py
└── directory1
    └── module1.py
└── directory2
    └── module2.py&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 먼저 이중점(..)을 이용하여 각 모듈이 서로를 참조하도록 할 수 있을 것이다. 외부에서 이 패키지를 사용하는 경우에는 문제없이 잘 동작할 것이다. 하지만 main.py에서 모듈을 이용하려는 경우 문제가 생길 수 있다. 이중점(..)이 루트 디렉터리를 벗어날 수 없기 때문에 생기는 문제이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우, __init__.py에 현재 패키지 디렉터리를 추가하여 해결하는 방법이 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1716988754016&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import sys
sys.path.append('/path/to/directory')

import my_module&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면, main.py에서도 내부 모듈을 이용할 수 있고, 외부 프로젝트에서도 모듈 임포트 코드 변경 없이 패키지를 이용할 수 있다.&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;&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;&lt;a href=&quot;https://fortierq.github.io/python-import/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://fortierq.github.io/python-import/&lt;/a&gt;&lt;/p&gt;</description>
      <category>프로그래밍/Python</category>
      <author>AlgorFati</author>
      <guid isPermaLink="true">https://algorfati.tistory.com/226</guid>
      <comments>https://algorfati.tistory.com/226#entry226comment</comments>
      <pubDate>Wed, 29 May 2024 21:16:09 +0900</pubDate>
    </item>
    <item>
      <title>[OS] 운영체제 #1 - 운영체제란 무엇인가?</title>
      <link>https://algorfati.tistory.com/224</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;운영체제란 무엇인가?&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제는 컴퓨터 프로그램이 하드웨어 자원을 이용할 수 있도록 하는 특수 소프트웨어 계층이다. 이는 컴퓨터 하드웨어와 사용자 응용 프로그램 사이에서 중개자 역할을 하며, 소프트웨어가 하드웨어와 일관되고 효율적으로 상호 작용할 수 있도록 한다. 운영 체제는 메모리 관리, 입력 및 출력 장치 처리와 같은 물리적인 인터페이스와 이를 추상화한 소프트웨어적인 인터페이스를 제공한다. 인기있는 운영 체제의 예로는 Windows, macOS, Linux 및 Android가 있다.&amp;nbsp;&lt;br&gt;컴퓨터 게임을 하는 상황을 가정해보자. 키보드와 마우스를 통해 게임 캐릭터를 움직여주고 화면을 통해 출력되는 게임 화면을 본다. 내부적으로는 메모리에 게임 데이터가 적재된다. 게임을 플레이하다 저장 버튼을 누르면 디스크에 현재까지 게임이 플레이된 내용이 저장된다. 이와 같은 과정들은 어떻게 구현되는 것일까?&amp;nbsp;&lt;br&gt;운영체제는 키보드, 마우스, 모니터, 메모리, 파일 등등과 같은 하드웨어 자원들을 이용할 수 있도록 추상화된 인터페이스를 제공한다. 키보드 인풋이 감지되면 키보드 디바이스 드라이버를 통해 입력된 데이터를 어플리케이션 레벨까지 전달시킬 수 있도록 한다. 게임에서 메모리를 요구하는 상황이 오면 가상 메모리를 거쳐 실제 메모리의 공간을 할당해준다. 화면에 게임을 렌더링해야하는 상황이 오면 출력시킬 데이터를 그래픽 드라이버를 통해 모니터로 뿌려준다. 운영체제는 이처럼 다양한 하드웨어 자원들을 관리하며 필요하다면 어플리케이션에서 시스템 함수들을 사용하여 운영체제를 통해 하드웨어 자원을 이용할 수 있도록 돕기도 한다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;운영체제가 하드웨어 자원을 추상화된 인터페이스로 제공함으로써 갖는 장점들은 어떤 것들이 있을까?&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;프로그램이 특정 하드웨어에 종속되지 않도록 한다.&lt;/b&gt;&lt;br&gt;운영체제라는 추상화 레이어가 없다면, 개발자가 작성한 코드는 그 코드를 돌리기 위한 하드웨어에 종속된다. 즉, 이 소프트웨어는 해당 하드웨어에서만 동작하고, 다른 하드웨어에서는 동작하지 않을 것이다.&lt;br&gt;(RAM을 갈아끼면 동작하지 못하고, 모니터를 바꾸면 동작하지 않는 게임을 상상해보자.)&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;하드웨어 자원 관리에 대한 개발에 대해 신경쓰지 않아도 된다.&lt;/b&gt;&lt;br&gt;운영체제는 하드웨어 자원 관리에 대한 수 많은 작업들을 담당하고 있다. 이 과정은 어플리케이션 레벨 아래에서 일어나며, 일반적으로는 개발자가 크게 신경쓰지 않아도 알아서 관리된다.&lt;br&gt;(new Object() 할 때마다 RAM의 현재 메모리 적재 상태를 확인해가며 메모리를 할당해야한다면 어떨까?)&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;보안과 안정성을 관리하는 시스템을 제공한다.&lt;/b&gt;&lt;br&gt;운영체제는 하드웨어 접근을 제어하고 관리함으로써, 애플리케이션들이 시스템 자원을 안전하고 효율적으로 사용할 수 있도록 한다. 또한, 잘못된 하드웨어 접근으로부터 시스템을 보호할 수 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;자원 관리의 최적화&lt;/b&gt;&lt;br&gt;운영체제는 메모리, CPU 시간, 디스크 공간 등의 자원을 효율적으로 관리하고 할당한다. 이는 시스템의 전반적인 성능을 향상시키고, 자원 충돌과 낭비를 최소화한다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;하드웨어 이용을 위한 사용자 인터페이스 제공&lt;/b&gt;&lt;br&gt;운영체제는 사용자와 하드웨어 사이의 상호 작용을 위한 인터페이스를 제공한다. 이는 사용자가 시스템을 보다 쉽게 조작할 수 있게 하며, 그래픽 사용자 인터페이스(GUI)는 이를 더욱 직관적으로 만든다.&lt;br&gt;&amp;nbsp;&lt;br&gt;이처럼 운영체제는 마법사(Illusionist)와 같은 역할을 한다. 운영체제는 어렵고 복잡한 하드웨어에 대한 깔끔하고 쉬운 추상화를 제공하며, 사용자들로 하여금 무한한 메모리를 제공할 수 있는 것처럼 동작한다. 또한 파일과 같은 인터페이스를 제공하여 실제 데이터가 어떤 형태의 디스크에 저장되는지와 같이 특수적인 상황에 대해 걱정할 필요가 없도록 한다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;운영체제의 기능&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제는 하드웨어의 자원들을 추상화한 인터페이스로 제공하는 특수한 소프트웨어라 정의하였었다. 그렇다면 운영체제가 어떤 하드웨어들을 어떤 개념들로 추상화하는지 알아보자.&amp;nbsp;&lt;br&gt;운영체제는 하드웨어 프로세서(CPU)를 &lt;b&gt;스레드(Thread)&lt;/b&gt;의 개념으로 추상화하여 제공한다. 스레드는 명령어 집합(ISA)에 정의된 명령어대로 코드를 실행하는 실행의 한 단위이다. 실행이라는 과정을 위해 프로세서로부터 계산을 위한 자원을 공급받는다. 사용자들은 스레드라는 인터페이스를 통해서 하드웨어 프로세서의 자원들을 활용할 수 있다.&lt;br&gt;하드웨어 메모리(RAM ..)를 &lt;b&gt;주소 공간(Address Spaces)&lt;/b&gt;의 개념으로 추상화하여 제공한다. 주소 공간은 메모리에 접근할 수 있는 인터페이스로, 사용자들은 이 공간을 활용하여 하드웨어 메모리에 데이터를 할당 및 해제할 수 있다.&lt;br&gt;하드웨어 저장공간(Disk ..)를 &lt;b&gt;파일(File)&lt;/b&gt;의 개념으로&amp;nbsp; 추상화하여 제공한다. 사용자는 USB에 데이터를 저장하든, 디스크에 데이터를 저장하든 신경쓸 필요 없이 파일이라는 통일된 인터페이스를 통해 다양한 스토리지들에 데이터를 저장할 수 있다.&lt;br&gt;하드웨어 네트워크 카드를 &lt;b&gt;소켓(Socket)&lt;/b&gt;의 개념으로 추상화하여 제공한다. 사용자는 네트워크 카드를 통해 외부 컴퓨터와 통신해야하는 상황을 소켓 인터페이스를 활용하여 해결할 수 있다.&lt;br&gt;또한 그 외 다양한 하드웨어 자원들을 여러 종류의 &lt;b&gt;디바이스 드라이버 (Device Driver)&lt;/b&gt;로 추상화하여 제공한다. 사용자는 키보드, 마우스, 사운드와 같은 I/O 장치들을 디바이스 드라이버라는 인터페이스를 통해 사용할 수 있다.&lt;br&gt;운영체제는 이와 같은 다양한 기능들을 활용하는 프로그램 인스턴스를 &lt;b&gt;프로세스(Process)&lt;/b&gt;의 개념으로 추상화하여 제공한다. 프로세스는 운영체제의 각종 자원들을 활용하는 하나의 독립된 단위로, 사용자들로 하여금 각종 자원들을 효과적으로 활용할 수 있도록 한다.&amp;nbsp;&lt;br&gt;사용자는 시스템 라이브러리를 이용하여 운영체제의 자원을 활용하는 프로그램을 작성할 수 있다. 이렇게 작성된 프로그램은 운영체제에서 프로세스라는 단위로 인스턴싱되어 관리되고, 사용자가 작성한 일련의 동작을 수행한다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1514&quot; data-origin-height=&quot;671&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7oL3z/btsGEik6xJb/mdIlmXLn1mNejRQaWFsMHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7oL3z/btsGEik6xJb/mdIlmXLn1mNejRQaWFsMHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7oL3z/btsGEik6xJb/mdIlmXLn1mNejRQaWFsMHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7oL3z%2FbtsGEik6xJb%2FmdIlmXLn1mNejRQaWFsMHk%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;1514&quot; height=&quot;671&quot; data-origin-width=&quot;1514&quot; data-origin-height=&quot;671&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;운영체제는 여러 개의 프로세스들을 관리한다. 각 프로세스들은 서로 독립된 메모리 공간을 이용하며 사용되는 공간들은 운영체제에 의해 보호된다. 서로가 서로의 메모리에 접근하거나, 특정 프로세스가 잡고있는 파일에 접근하는 상황들이 보호되기 때문에, 수 많은 프로그램들이 동시에 동작하더라도 서로 영역을 침범함으로써 생기는 문제들에 대해 안전하다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1534&quot; data-origin-height=&quot;717&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nNmhU/btsGDzm5p08/cnQ7NkOKEE3C974pxmHvaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nNmhU/btsGDzm5p08/cnQ7NkOKEE3C974pxmHvaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nNmhU/btsGDzm5p08/cnQ7NkOKEE3C974pxmHvaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnNmhU%2FbtsGDzm5p08%2FcnQ7NkOKEE3C974pxmHvaK%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;1534&quot; height=&quot;717&quot; data-origin-width=&quot;1534&quot; data-origin-height=&quot;717&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;또한 운영체제는 여러 개의 프로세스들이 동시에 동작하도록 한다. 운영체제는 어떻게 이것을 가능하게 할 수 있을까?&lt;br&gt;하나의 CPU에 하나의 프로세서만 있다고 가정해보자.&amp;nbsp;&lt;br&gt;먼저 운영체제는 프로세서로 Process1의 컴파일된 코드를 실행시킨다. 이때 프로세서를 통해 참조되는 메모리도 Process1의 메모리이다. Process1을 잠깐동안 실행시킨 후, 프로세서는 Process2를 실행시킨다. 참조되는 메모리도 Process2의 메모리로 바뀐다. 또 잠깐 동안 실행시킨 후, 다시 Process1로 되돌아가고 이 과정을 반복한다. CPU 프로세서의 처리속도는 엄청나게 빠르기 때문에, 지금 과정을 빠르게 수행한다면 여러 개의 프로세스들이 동시에 동작하는것처럼 보이게 된다. 운영체제는 이와 같은 방법으로 하나의 프로세서만 갖고도 프로세스들을 동시에 동작시킬 수 있다.&lt;/p&gt;</description>
      <category>프로그래밍/OS</category>
      <category>Operating System</category>
      <category>OS</category>
      <category>운영체제</category>
      <author>AlgorFati</author>
      <guid isPermaLink="true">https://algorfati.tistory.com/224</guid>
      <comments>https://algorfati.tistory.com/224#entry224comment</comments>
      <pubDate>Mon, 15 Apr 2024 15:42:29 +0900</pubDate>
    </item>
    <item>
      <title>[C#] Nullable 타입</title>
      <link>https://algorfati.tistory.com/222</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;C# Nullable 타입&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 개발 세계에서 null 참조는 꽤나 골치아픈 문제로, 개발자들 사이에서 악명 높은 &quot;NullReferenceException&quot;으로 이어질 수 있다. 지속적으로 발전하고 있는 C# 언어는 이러한 문제를 해결하기 위해 안전성과 개발자의 생산성을 향상시키는 강력한 기능들을 도입하고 있다. C#의 최신 버전에서 주목할 만한 발전 중 하나는 nullability 기능의 도입 및 강화이다.&amp;nbsp; &lt;br /&gt;이번에는 C#에서 nullability의 중요성을 다루며, 코드 안전성과 명확성을 어떻게 향상시키는지 실용적인 예시와 함께 살펴본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Nullable을&amp;nbsp;활용해&amp;nbsp;Non-Nullable&amp;nbsp;타입을&amp;nbsp;Nullable로&amp;nbsp;만들기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C#에서&amp;nbsp;타입&amp;nbsp;시스템은&amp;nbsp;변수가&amp;nbsp;null&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;int,&amp;nbsp;double,&amp;nbsp;bool&amp;nbsp;등)은&amp;nbsp;기본적으로&amp;nbsp;null을&amp;nbsp;받을&amp;nbsp;수&amp;nbsp;없는&amp;nbsp;non-nullable&amp;nbsp;타입이다.&amp;nbsp;하지만,&amp;nbsp;데이터베이스&amp;nbsp;작업이나&amp;nbsp;외부&amp;nbsp;API&amp;nbsp;호출&amp;nbsp;등에서&amp;nbsp;이러한&amp;nbsp;값&amp;nbsp;타입들이&amp;nbsp;null&amp;nbsp;값을&amp;nbsp;가질&amp;nbsp;필요가&amp;nbsp;있는&amp;nbsp;경우가&amp;nbsp;종종&amp;nbsp;있다.&amp;nbsp;이런&amp;nbsp;상황에서&amp;nbsp;C#의&amp;nbsp;Nullable&amp;lt;T&amp;gt;&amp;nbsp;구조체는&amp;nbsp;값을&amp;nbsp;가질&amp;nbsp;수도&amp;nbsp;있고&amp;nbsp;null일&amp;nbsp;수도&amp;nbsp;있는&amp;nbsp;값을&amp;nbsp;다루기&amp;nbsp;쉽게&amp;nbsp;해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Nullable&amp;lt;T&amp;gt;&amp;nbsp;알아보기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nullable&amp;lt;T&amp;gt;는 값 타입 T에 null을 할당할 수 있게 해주는 일반적인 구조체다. 이를 통해 값 타입에 '정의되지 않음'이나 '값 없음' 같은 추가 상태를 부여할 수 있다.&lt;br /&gt;사용 방법은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1709646837790&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Nullable&amp;lt;int&amp;gt; nullableInt = null;
int? anotherNullableInt = 5; // int?는 Nullable&amp;lt;int&amp;gt;의 축약형이다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서&amp;nbsp;int?는&amp;nbsp;Nullable&amp;lt;int&amp;gt;를&amp;nbsp;더&amp;nbsp;간단하게&amp;nbsp;쓰는&amp;nbsp;방법이다.&amp;nbsp;C#에서&amp;nbsp;?를&amp;nbsp;붙이면&amp;nbsp;해당&amp;nbsp;타입을&amp;nbsp;Nullable&amp;lt;T&amp;gt;로&amp;nbsp;자동&amp;nbsp;변환한다. &lt;br /&gt;&lt;br /&gt;Nullable 타입 변수가 null인지 아닌지를 확인하고, 값이 있으면 그 값을 사용하는 건 아주 간단하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 예제를 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1709646889089&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (nullableInt.HasValue)
{
    Console.WriteLine(nullableInt.Value); // nullableInt가 null이 아니면 여기서 값 사용
}
else
{
    Console.WriteLine(&quot;값 없음&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Nullable 참조 타입&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역사적으로 C#의 참조 타입은 null 값을 가질 수 있었고, 이는 의도하지 않은 null 참조를 역참조할 때 잠재적인 문제점으로 이어질 수 있었다. C# 8.0에서는 변화가 생겼는데. nullable 참조 타입이라는 기능이 도입되었고, 이를 통해 참조 타입이 nullable인지 아닌지를 명시적으로 표현할 수 있게 되었다. 이는 언어에 새로운 안전 수준을 더했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;참조 타입의 null 의도 표현하기 (string vs string?)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 string 타입을 고려해 보자. C# 8.0 이전에는 모든 문자열 변수에 null을 할당할 수 있었고, 컴파일러는 이에 대해 불만을 제기하지 않았다. 그렇기에 아래와 같이 의도하지 않게 null이 표현되는 상황이 발생할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1709644645643&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;string name = null; // Perfectly valid, but potentially risky&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;만약 nullable이 도입된다면 어떨까? 이 개념이 도입된다면 다음과 같은 컨벤션을 적용할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 기존처럼 string 타입을 사용하는것은 이 참조 타입이 null을 허용하지 않는 참조변수임을 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. string? 타입을 사용하는것은 이 참조 타입이 null이 될 수 있는 참조변수임을 나타낸다.&lt;/p&gt;
&lt;pre id=&quot;code_1709644966835&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// null을 허용하지 않는 참조타입
string notNullable = &quot;Hello, C#!&quot;;

// 명시적으로 nullable
string? nullable = null;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구분은 중요하다. 컴파일러와 동료 개발자에게 의도를 명확히 알릴 수 있으며, 컴파일러는 잠재적인 null 참조 예외를 방지하기 위해 이 정보를 사용하여 경고를 발생시킬 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;코드&amp;nbsp;안전성에서&amp;nbsp;Nullability의&amp;nbsp;역할&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nullability를 명시함으로써 C#은 여러 방면에서 코드 안전성을 향상시킨다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;1. 컴파일 시간 검사&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일러는 정적 분석을 수행하여 가능한 null 참조 역참조를 감지하고, 잠재적인 런타임 오류를 컴파일 시간 경고로 전환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;2. 코드 명확성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;non-nullable 변수를 선언함으로써 null 값이 예상되지 않음을 명확한 계약으로 표현하며, 이는 코드의 가독성과 유지보수성을 향상시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;3. 도구 개선&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IDE는 nullability 주석을 사용하여 더 나은 코드 완성, 리팩토링 및 분석을 제공하며, 개발자의 생산성을 더욱 향상시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;실용적인 예시들&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Nullable 참조 타입 사용하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1709645613518&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;string notNullable = &quot;값이 있어야 한다.&quot;;
string? nullable = null;

// 컴파일러 경고: 가능성이 있는 null 참조 역참조
Console.WriteLine(nullable.Length);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Null-Conditional 연산자 (?.)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nullable 참조의 멤버에 안전하게 접근하기 위해 null conditional 연산자를 이용하면 편하다.&lt;/p&gt;
&lt;pre id=&quot;code_1709645798341&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 다음 코드를 보자.
var p = man;
if(p != null)
{
   var result = man.Name;
}
else
{
   var result = null;
}

// 위 코드를 다음과 같이 표현할 수 있다.
var result = man?.Name;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Null-Coalescing 연산자 (??)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;null 병합 연산자(??)는 표현식이 null일 경우 기본값을 지정할 수 있는 간결한 방법을 제공한다. 이 연산자는 nullable 타입이나 초기화되지 않았을 수 있는 참조 타입을 다룰 때 특히 유용하다.&amp;nbsp;다음은&amp;nbsp;null&amp;nbsp;병합&amp;nbsp;연산자를&amp;nbsp;사용하는&amp;nbsp;방법을&amp;nbsp;보여주는&amp;nbsp;예시이다.&lt;/p&gt;
&lt;pre id=&quot;code_1709645726738&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Nullable 정수를 정의하고 null 값을 할당한다.
int? nullableInt = null;

// NullableInt가 null일 경우 기본값으로 10을 제공하기 위해 null 병합 연산자를 사용한다.
int result = nullableInt ?? 10;

Console.WriteLine(result);  // 출력: 10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ps.&lt;br /&gt;삼항연산자를&amp;nbsp;사용해도&amp;nbsp;null&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;vs&amp;nbsp;일반화의&amp;nbsp;차이이다.&amp;nbsp;null&amp;nbsp;병합&amp;nbsp;연산자는&amp;nbsp;null&amp;nbsp;체크&amp;nbsp;후에&amp;nbsp;null인&amp;nbsp;경우&amp;nbsp;기본값을&amp;nbsp;지정하기&amp;nbsp;위한&amp;nbsp;목적으로&amp;nbsp;사용되는&amp;nbsp;개념인&amp;nbsp;반면,&amp;nbsp;삼항연산자는&amp;nbsp;boolean&amp;nbsp;조건에&amp;nbsp;맞게&amp;nbsp;좀&amp;nbsp;더&amp;nbsp;일반적인&amp;nbsp;상황을&amp;nbsp;위한&amp;nbsp;목적으로&amp;nbsp;사용되는&amp;nbsp;개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Null-Coalescing assignment 연산자 (??=)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;null 병합 할당 연산자(??=)는 해당 변수가 현재 null인 경우에만 변수에 값을 할당하는 편리한 약어이다. 즉, 왼쪽 피연산자가 null인지 확인하고, null인 경우에만 오른쪽 피연산자의 값을 왼쪽 피연산자에 할당한다. 왼쪽 피연산자가 null이 아니면 연산자는 아무 작업도 수행하지 않는다.&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;Null 허용 변수가 있고 해당 변수에 값이 있는지 확인하려고 한다고 가정하자. 전통적으로 if 문을 사용하여 변수가 null인지 확인한 다음 null이면 값을 할당할 수 있다. ??= 연산자는 이 패턴을 단순화한다.&lt;/p&gt;
&lt;pre id=&quot;code_1709649075886&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;string? name = null;

// 전통적인 방법
if (name == null)
{
    name = &quot;Default Name&quot;;
}

// ??= operator 이용
name ??= &quot;Default Name&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Null-Forgiving 연산자 (!)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;null&amp;nbsp;허용&amp;nbsp;연산자(!)는&amp;nbsp;컴파일러의&amp;nbsp;null&amp;nbsp;분석을&amp;nbsp;재정의하는&amp;nbsp;데&amp;nbsp;사용된다.&amp;nbsp;이&amp;nbsp;연산자는&amp;nbsp;컴파일러에게&amp;nbsp;null&amp;nbsp;경고를&amp;nbsp;&quot;용서&quot;하도록&amp;nbsp;지시한다.&amp;nbsp;이는&amp;nbsp;기본적으로&amp;nbsp;개발자가&amp;nbsp;연산자&amp;nbsp;왼쪽의&amp;nbsp;식이&amp;nbsp;null이&amp;nbsp;아님을&amp;nbsp;보장한다는&amp;nbsp;의미이다.&amp;nbsp;이는&amp;nbsp;분석에도&amp;nbsp;불구하고&amp;nbsp;코드의&amp;nbsp;특정&amp;nbsp;지점에서&amp;nbsp;값이&amp;nbsp;null이&amp;nbsp;아닐&amp;nbsp;것이라고&amp;nbsp;확신한다고&amp;nbsp;컴파일러에&amp;nbsp;주장하는&amp;nbsp;방법이다. &lt;br /&gt;&lt;br /&gt;전체&amp;nbsp;동작에&amp;nbsp;대해&amp;nbsp;알아보자.&amp;nbsp;null&amp;nbsp;허용&amp;nbsp;참조&amp;nbsp;유형이&amp;nbsp;활성화되면(#nullable&amp;nbsp;enable)&amp;nbsp;C#&amp;nbsp;컴파일러는&amp;nbsp;잠재적인&amp;nbsp;null&amp;nbsp;참조&amp;nbsp;예외에&amp;nbsp;대해&amp;nbsp;경고하기&amp;nbsp;위해&amp;nbsp;정적&amp;nbsp;분석을&amp;nbsp;수행한다.&amp;nbsp;예를&amp;nbsp;들어&amp;nbsp;먼저&amp;nbsp;null을&amp;nbsp;확인하지&amp;nbsp;않고&amp;nbsp;null&amp;nbsp;허용&amp;nbsp;참조&amp;nbsp;유형을&amp;nbsp;역참조하려고&amp;nbsp;하면&amp;nbsp;컴파일러에서&amp;nbsp;경고가&amp;nbsp;표시된다. &lt;br /&gt;그러나 프로그램의 논리로 인해 값이 null이 아니라는 것을 알지만 컴파일러가 이를 확인할 수 없는 경우가 있을 수 있다. 이러한 상황에서는 null 허용 연산자를 사용하여 컴파일러 경고를 무음으로 설정할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1709650235557&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Suppose this method might return null, but due to program logic, it won't here.
string? nullableString = GetAString(); 

// The compiler warns about possible null dereference.
Console.WriteLine(nullableString.Length); 

// Using the null-forgiving operator to assert the value isn't null.
Console.WriteLine(nullableString!.Length); // No warning&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이&amp;nbsp;예에서&amp;nbsp;nullableString은&amp;nbsp;해당&amp;nbsp;유형&amp;nbsp;주석에&amp;nbsp;따라&amp;nbsp;null일&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;null&amp;nbsp;허용&amp;nbsp;문자열이다.&amp;nbsp;그러나&amp;nbsp;프로그램의&amp;nbsp;흐름에&amp;nbsp;따라&amp;nbsp;nullableString이&amp;nbsp;역참조될&amp;nbsp;때&amp;nbsp;null이&amp;nbsp;되지&amp;nbsp;않는다는&amp;nbsp;것을&amp;nbsp;알고&amp;nbsp;있다고&amp;nbsp;가정해&amp;nbsp;보자.&amp;nbsp;nullableString!&amp;nbsp;이&amp;nbsp;표현식에&amp;nbsp;대한&amp;nbsp;null&amp;nbsp;허용&amp;nbsp;여부&amp;nbsp;경고를&amp;nbsp;무시하도록&amp;nbsp;컴파일러에&amp;nbsp;지시한다. &lt;br /&gt;&lt;br /&gt;이&amp;nbsp;연산자를&amp;nbsp;사용하는&amp;nbsp;경우&amp;nbsp;다음과&amp;nbsp;같은&amp;nbsp;부분들이&amp;nbsp;고려되어야한다. &lt;br /&gt;1.&amp;nbsp;null&amp;nbsp;허용&amp;nbsp;연산자는&amp;nbsp;값이&amp;nbsp;null이&amp;nbsp;아니라고&amp;nbsp;확신하는&amp;nbsp;경우에만&amp;nbsp;드물게&amp;nbsp;사용해야&amp;nbsp;한다.&amp;nbsp;이를&amp;nbsp;과도하게&amp;nbsp;사용하면&amp;nbsp;코드에서&amp;nbsp;실제&amp;nbsp;null&amp;nbsp;허용&amp;nbsp;여부&amp;nbsp;문제가&amp;nbsp;숨겨&amp;nbsp;잠재적인&amp;nbsp;런타임&amp;nbsp;예외가&amp;nbsp;발생할&amp;nbsp;수&amp;nbsp;있다. &lt;br /&gt;&lt;br /&gt;2.&amp;nbsp;null&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;IL&amp;nbsp;코드에&amp;nbsp;영향을&amp;nbsp;주거나&amp;nbsp;런타임&amp;nbsp;검사를&amp;nbsp;추가하지&amp;nbsp;않는다. &lt;br /&gt;&lt;br /&gt;3.&amp;nbsp;사실&amp;nbsp;null&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;좋다. &lt;br /&gt;&lt;br /&gt;요약하자면,&amp;nbsp;Null&amp;nbsp;허용&amp;nbsp;연산자는&amp;nbsp;컴파일러에&amp;nbsp;Null&amp;nbsp;허용&amp;nbsp;여부를&amp;nbsp;주장하는&amp;nbsp;데&amp;nbsp;유용한&amp;nbsp;도구이지만&amp;nbsp;Null&amp;nbsp;허용&amp;nbsp;여부와&amp;nbsp;관련하여&amp;nbsp;코드의&amp;nbsp;안전성과&amp;nbsp;견고성을&amp;nbsp;유지하려면&amp;nbsp;신중하게&amp;nbsp;사용해야&amp;nbsp;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C#에서 nullable 기능의 도입과 지속적인 강화는 더 안전하고 예측 가능한 코드를 작성하는 방향으로의 중요한 발전이다. nullable 참조 타입을 활용함으로써 개발자는 강력한 도구 세트를 얻게 되어 의도를 명확히 표현하고, 런타임 오류를 줄이며, 코드 기반을 명확히 할 수 있다. 소프트웨어 개발의 복잡성을 계속해서 탐색함에 따라, 이러한 기능들은 C#의 안전성, 명확성 및 개발자 효율성에 대한 약속을 강조한다. 이러한 Convention을 채택함으로써 애플리케이션을 더 견고하게 만들 뿐만 아니라 코드의 공동 이해와 유지보수성을 향상시킬 수 있다.&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;ps.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 기존처럼 일반 참조 변수에 null을 넣는 것은 지양해야할 것이다. 그리고 언급한 null 관련 연산자들에 대해 완벽하게 숙지하지 않는다면 최신 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;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍/C#</category>
      <category>c#</category>
      <category>Nullable</category>
      <author>AlgorFati</author>
      <guid isPermaLink="true">https://algorfati.tistory.com/222</guid>
      <comments>https://algorfati.tistory.com/222#entry222comment</comments>
      <pubDate>Thu, 7 Mar 2024 00:51:58 +0900</pubDate>
    </item>
    <item>
      <title>[C#] 불변성 처리</title>
      <link>https://algorfati.tistory.com/221</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;C# 불변성 처리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어&amp;nbsp;개발이&amp;nbsp;발전하는&amp;nbsp;과정&amp;nbsp;속에서,&amp;nbsp;불변성의&amp;nbsp;원칙은&amp;nbsp;견고하고&amp;nbsp;thread-safe한&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;C#의&amp;nbsp;새로운&amp;nbsp;기능들,&amp;nbsp;그리고&amp;nbsp;readonly&amp;nbsp;및&amp;nbsp;const와&amp;nbsp;같은&amp;nbsp;다른&amp;nbsp;C#&amp;nbsp;구문과의&amp;nbsp;차이점을&amp;nbsp;알아볼&amp;nbsp;것이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;불변성의 장점&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불변성의&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;보장은&amp;nbsp;불변&amp;nbsp;객체를&amp;nbsp;본질적으로&amp;nbsp;thread-safe&amp;nbsp;하게&amp;nbsp;만들어,&amp;nbsp;복잡한&amp;nbsp;lock&amp;nbsp;메커니즘의&amp;nbsp;필요성을&amp;nbsp;줄이고&amp;nbsp;race&amp;nbsp;condition의&amp;nbsp;위험을&amp;nbsp;크게&amp;nbsp;줄인다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;C#의 불변성을 위한 기능&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C#은&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;제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Record Types&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C#&amp;nbsp;9에서&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;제공한다. &lt;br /&gt;&lt;br /&gt;다음은&amp;nbsp;record&amp;nbsp;type의&amp;nbsp;정의이다.&amp;nbsp;일반적인&amp;nbsp;객체와&amp;nbsp;달리&amp;nbsp;한&amp;nbsp;줄로&amp;nbsp;정의할&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;pre id=&quot;code_1709639449228&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public record Person(string FirstName, string LastName, DateTime DateOfBirth);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은&amp;nbsp;record&amp;nbsp;type의&amp;nbsp;사용&amp;nbsp;예제이다.&lt;/p&gt;
&lt;pre id=&quot;code_1709639489338&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void Main(string[] args)
{
    // Creating instances of the Person record
    var person1 = new Person(&quot;John&quot;, &quot;Doe&quot;, new DateTime(1980, 1, 1));
    var person2 = new Person(&quot;Jane&quot;, &quot;Doe&quot;, new DateTime(1985, 5, 23));

    // Displaying the person records using the built-in ToString implementation
    Console.WriteLine(person1); // Output: Person { FirstName = John, LastName = Doe, DateOfBirth = 1/1/1980 12:00:00 AM }
    Console.WriteLine(person2); // Output: Person { FirstName = Jane, LastName = Doe, DateOfBirth = 5/23/1985 12:00:00 AM }

    // Demonstrating value-based equality
    var person3 = new Person(&quot;John&quot;, &quot;Doe&quot;, new DateTime(1980, 1, 1));
    Console.WriteLine(person1 == person3); // Output: True, because person1 and person3 have the same values for all properties

    // Creating a modified copy using with-expression
    var person4 = person1 with { FirstName = &quot;Jake&quot; };
    Console.WriteLine(person4); // Output: Person { FirstName = Jake, LastName = Doe, DateOfBirth = 1/1/1980 12:00:00 AM }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Immutable Collections&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;System.Collections.Immutable&amp;nbsp;네임스페이스는&amp;nbsp;ImmutableList,&amp;nbsp;ImmutableDictionary&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;반환한다. &lt;br /&gt;&lt;br /&gt;다음은&amp;nbsp;ImmutableList에&amp;nbsp;대한&amp;nbsp;예제이다.&lt;/p&gt;
&lt;pre id=&quot;code_1709639518754&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;
using System.Collections.Immutable;

class Program
{
    static void Main()
    {
        // Create an immutable list with some initial elements.
        ImmutableList&amp;lt;int&amp;gt; immutableList = ImmutableList.Create(1, 2, 3);

        // Display the initial list
        Console.WriteLine(&quot;Original ImmutableList:&quot;);
        foreach (var item in immutableList)
        {
            Console.WriteLine(item);
        }

        // Attempt to add a new element. This does not modify the original list but returns a new one.
        ImmutableList&amp;lt;int&amp;gt; newList = immutableList.Add(4);

        // Display the original list to show it has not changed
        Console.WriteLine(&quot;\nOriginal ImmutableList after attempting to add:&quot;);
        foreach (var item in immutableList)
        {
            Console.WriteLine(item);
        }

        // Display the new list to show the addition
        Console.WriteLine(&quot;\nNew ImmutableList with the added element:&quot;);
        foreach (var item in newList)
        {
            Console.WriteLine(item);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Init-Only Setters&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C# 9에서도 init-only 설정자가 도입되었다.&amp;nbsp; &lt;br /&gt;init&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;한다. &lt;br /&gt;이는&amp;nbsp;객체&amp;nbsp;생성&amp;nbsp;중에&amp;nbsp;유연성과&amp;nbsp;그&amp;nbsp;후의&amp;nbsp;불변성&amp;nbsp;사이의&amp;nbsp;균형을&amp;nbsp;이루게&amp;nbsp;한다. &lt;br /&gt;&lt;br /&gt;C#&amp;nbsp;9에는&amp;nbsp;init&amp;nbsp;키워드로&amp;nbsp;표시된&amp;nbsp;init&amp;nbsp;전용&amp;nbsp;setter도&amp;nbsp;도입되었다.&amp;nbsp; &lt;br /&gt;이를&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;유지한다. &lt;br /&gt;&lt;br /&gt;다음은&amp;nbsp;init&amp;nbsp;only&amp;nbsp;setters를&amp;nbsp;이용하여&amp;nbsp;불변타입을&amp;nbsp;초기화하는&amp;nbsp;예제이다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1709639580074&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public record Description(string Desc);

public class Library
{
	public string Name { get; init; }
	public ImmutableArray&amp;lt;string&amp;gt; Books { get; init; }
	public Description Description { get; init; }

}


// how to instantiate
var library = new Library()
{
    Name = &quot;Downtown Library&quot;,
    Books = ImmutableArray.Create(new[] { &quot;Book1&quot;, &quot;Book2&quot;, &quot;Book3&quot; }),
    Description = new Description(&quot;blabla&quot;)
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;readonly 및 const와 비교&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C#의 readonly 및 const 키워드는 불변성으로 가는 경로처럼 보일 수 있지만, 더 구체적이고 제한된 목적을 가지고 있다. &lt;br /&gt;예를&amp;nbsp;들어,&amp;nbsp;readonly&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; &lt;br /&gt;&lt;br /&gt;다음은&amp;nbsp;readonly를&amp;nbsp;이용하는&amp;nbsp;경우에도&amp;nbsp;불변성을&amp;nbsp;갖추지&amp;nbsp;못할&amp;nbsp;수&amp;nbsp;있다는&amp;nbsp;것을&amp;nbsp;보여주는&amp;nbsp;예제이다.&lt;/p&gt;
&lt;pre id=&quot;code_1709639615415&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ExampleClass
{
    public readonly List&amp;lt;int&amp;gt; Numbers = new List&amp;lt;int&amp;gt;();

    public ExampleClass()
    {
        // Allowed: Modifying the content of the readonly field.
        Numbers.Add(1);
    }

    public void AddNumber(int number)
    {
        // Allowed: Because we're not changing the reference of the readonly field, just the object's state.
        Numbers.Add(number);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의&amp;nbsp;예에서&amp;nbsp;Numbers는&amp;nbsp;readonly이고&amp;nbsp;생성&amp;nbsp;후&amp;nbsp;다른&amp;nbsp;List&amp;lt;int&amp;gt;&amp;nbsp;인스턴스에&amp;nbsp;재할당될&amp;nbsp;수&amp;nbsp;없지만,&amp;nbsp;List의&amp;nbsp;내용은&amp;nbsp;계속&amp;nbsp;수정할&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;readonly로는&amp;nbsp;참조&amp;nbsp;타입에&amp;nbsp;대한&amp;nbsp;불변성을&amp;nbsp;갖출&amp;nbsp;수&amp;nbsp;없다. &lt;br /&gt;&lt;br /&gt;const는&amp;nbsp;어떨까?&amp;nbsp;const&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;const&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;상황에는&amp;nbsp;적용이&amp;nbsp;불가능하다. &lt;br /&gt;&lt;br /&gt;그에&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;강조한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C#에서 불변성은 안전하고 효율적이며 신뢰할 수 있는 애플리케이션을 구축하기 위해 개발자에게 필요한 도구를 제공하는 데 목표를 두고 있다. 레코드 타입, 불변 컬렉션 및 init-only 설정자와 같은 기능을 활용함으로써, C# 개발자들은 불변성의 힘을 활용하여 동시성 문제와 현대 소프트웨어 개발의 복잡성을 견딜 수 있는 코드를 만들 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍/C#</category>
      <category>c#</category>
      <category>불변성</category>
      <author>AlgorFati</author>
      <guid isPermaLink="true">https://algorfati.tistory.com/221</guid>
      <comments>https://algorfati.tistory.com/221#entry221comment</comments>
      <pubDate>Tue, 5 Mar 2024 20:39:59 +0900</pubDate>
    </item>
    <item>
      <title>[C#] 비동기 프로그래밍</title>
      <link>https://algorfati.tistory.com/220</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;C#&amp;nbsp;비동기&amp;nbsp;프로그래밍&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C#의 async와 await 키워드는 비동기 작업을 다루는 과정을 간소화하여 비동기 코드를 더 읽기 쉽고 유지보수하기 쉽게 만드는 데 사용된다. 비동기 프로그래밍은 시간이 많이 걸리는 작업(예: I/O 작업, 웹 요청, 데이터베이스 트랜잭션 등)을 메인 스레드를 차단하지 않고 실행할 수 있게 해주므로, 사용자 인터페이스의 반응성을 유지하고 서버 측 응용 프로그램의 확장성을 향상시키는 데 필수적이다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;async와 await&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;async 키워드는 메소드, 람다 표현식 또는 익명 메소드를 비동기적으로 표시하는 데 사용된다. async 메소드는 호출자의 스레드를 차단하지 않고 잠재적으로 오래 걸리는 작업을 수행하는 편리한 방법을 제공한다. async 메소드는 await 표현식에 도달할 때까지 동기적으로 실행되며, 그 시점에서 호출자에게 제어를 반환하고 대기 중인 작업이 완료될 때까지 기다린다.&lt;br /&gt;&lt;br /&gt;await 키워드는 비동기 메소드 내의 작업에 적용되어 대기 중인 작업이 완료될 때까지 메소드의 실행을 일시 중지한다. Task는 진행 중인 작업을 나타낸다. await 연산자는 메소드가 실행 중인 스레드를 차단하지 않는다. 대신, 컴파일러는 대기 중인 작업에 메소드의 나머지 부분을 연속으로 등록하고 즉시 호출자에게 반환한다.&lt;br /&gt;&lt;br /&gt;async와 await 작동 방식은 다음과 같다.&lt;br /&gt;async 메소드가 호출되면, 호출자에게 즉시 Task(또는 메소드가 값을 반환하는 경우 Task&amp;lt;T&amp;gt;)를 반환한다. Task는 진행 중인 작업을 나타낸다.(C++의 Future와 대응되는 개념) async 메소드 내에서 대기 중인 작업에 await 연산자가 적용되고, 대기 중인 작업이 완료될 때까지 메소드의 실행이 일시 중지된다.&amp;nbsp; 작업이 완료되면, await 지점부터 실행이 재개된다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;여기에서 주목할 점은 await 시에 스레드가 차단되지 않는다는점이다. 비차단의 장점을 이해하기 위해 일반적인 멀티스레딩 비동기 방식에서부터 얘기를 시작해보자. 여러개의 스레드를 생성하고 IO 바운드 되어있는 작업을 비동기로 처리하도록 하는 경우 이 작업을 맡은 스레드는 IO작업이 끝날 때까지 차단될 것이다. 물론 이 스레드가 차단되더라도 다른 스레드들이 남아있기 때문에 여전히 멀티스레딩의 장점은 존재한다. 하지만 이런 IO 작업이 무수히 많아지는 경우, 스레드도 무수히 많이 생성하는것은 비효율적이다. 반면, await은 코드가 해당 지점에서 대기하면서 동시에 스레드를 차단하지 않기 때문에, 적당한 수의 스레드만 생성하더라도 많은 IO 작업들을 다룰 수 있다. 이것이 비차단의 장점이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이 예시에서, GetWebPageContentAsync는 웹 페이지의 내용을 비동기적으로 검색하는 async 메소드이다. await 키워드는 스레드를 차단하지 않고 작업이 완료될 때까지 비동기적으로 대기하는 데 사용된다. 작업이 완료되면, await 지점부터 메소드가 계속된다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;public async Task&amp;lt;string&amp;gt; GetWebPageContentAsync(string url)
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;using (HttpClient client = new HttpClient())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;string content = await client.GetStringAsync(url); // 웹 페이지의 내용을 비동기적으로 가져온다.
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return content; // 위의 비동기 작업이 완료되면 메소드의 실행이 이 지점에서 재개된다.
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 여기서 예외를 잡을 필요가 없다면 호출 메소드에서 처리하면 된다.
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;async, await의 장점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;응답성 향상&lt;/b&gt;&lt;br /&gt;UI 애플리케이션에서 비동기 작업을 사용하면 긴 작업(예: 네트워크 요청, 파일 I/O 작업)이 UI 스레드를 차단하지 않기 때문에 애플리케이션이 더 반응적으로 유지된다. 사용자는 작업이 백그라운드에서 수행되는 동안 인터페이스와 상호작용할 수 있다. &lt;br /&gt;&lt;br /&gt;&lt;b&gt;스케일러빌리티 향상&lt;/b&gt;&lt;br /&gt;서버 측 애플리케이션(예: 웹 API)에서 async와 await을 사용하면 동시에 처리할 수 있는 요청의 수가 증가한다. 이는 작업이 I/O 바인딩 작업을 기다리는 동안 스레드가 차단되지 않고 다른 요청을 처리할 수 있기 때문이다. &lt;br /&gt;&lt;br /&gt;&lt;b&gt;코드 가독성 및 유지 관리&lt;/b&gt;&lt;br /&gt;async와 await을 사용하면 전통적인 비동기 프로그래밍 모델(예: 콜백 함수 사용)에 비해 코드를 읽고 이해하기 쉽다. 비동기 코드가 동기 코드와 유사하게 보이고 작성될 수 있어, 코드의 가독성과 유지 관리가 쉬워진다.&lt;br /&gt;전통적인 비동기 프로그래밍 모델(콜백함수 사용)에서는 비동기 함수를 작성하기 위해서 콜백함수를 새롭게 정의해야하고, 이는 중첩된 콜백 구조를 만들기 때문에 코드의 가독성을 저하시킨다.&lt;br /&gt;하지만 async, await을 이용한다면 다르다. async 키워드를 통해 이 함수가 비동기 루틴을 이용한다는 사실을 쉽게 파악할 수 있고, await 키워드를 통해 비동기 처리 루틴들을 순차적으로 표현할 수 있는데, 이는 거의 동기 코드와 비슷할 정도로 쉽게 표현될 수 있다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;자원 사용 최적화&lt;/b&gt;&lt;br /&gt;비동기 프로그래밍은 필요한 경우에만 추가 스레드를 사용하므로 자원 사용이 최적화된다. 이는 특히 I/O 바운드 작업에서 중요한데, 이러한 작업은 CPU 시간보다는 대기 시간이 많이 필요하기 때문이다. &lt;br /&gt;&lt;br /&gt;&lt;b&gt;간결한 예외 처리&lt;/b&gt;&lt;br /&gt;async 메소드에서 발생한 예외는 await 표현식을 사용하는 호출 코드에서 캐치할 수 있으므로, 예외 처리 로직을 더 간결하게 유지할 수 있다. &lt;br /&gt;&lt;br /&gt;&lt;b&gt;비동기 작업의 조합&lt;/b&gt;&lt;br /&gt;async와 await을 사용하면 여러 비동기 작업을 쉽게 조합하고 조정할 수 있다. 예를 들어, Task.WhenAll 메소드를 사용하여 여러 비동기 작업이 모두 완료될 때까지 기다릴 수 있으며, 이는 복잡한 비동기 로직을 구현할 때 유용하다. &lt;br /&gt;&lt;br /&gt;&lt;b&gt;컨텍스트 전환 최소화&lt;/b&gt;&lt;br /&gt;ConfigureAwait(false)를 사용함으로써 UI 스레드와 같은 특정 컨텍스트로의 불필요한 전환을 방지할 수 있어, 성능을 향상시킬 수 있다. 이는 라이브러리나 백그라운드 작업에서 특히 유용하다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.reddit.com/r/AskProgramming/comments/15wf5tl/c_asyncawait_vs_background_threads/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.reddit.com/r/AskProgramming/comments/15wf5tl/c_asyncawait_vs_background_threads/&lt;/a&gt;&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;a href=&quot;https://blog.stephencleary.com/2013/11/there-is-no-thread.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blog.stephencleary.com/2013/11/there-is-no-thread.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍/C#</category>
      <category>c#</category>
      <category>비동기 프로그래밍</category>
      <author>AlgorFati</author>
      <guid isPermaLink="true">https://algorfati.tistory.com/220</guid>
      <comments>https://algorfati.tistory.com/220#entry220comment</comments>
      <pubDate>Mon, 4 Mar 2024 23:39:15 +0900</pubDate>
    </item>
  </channel>
</rss>