Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

스레드 간의 read/write 시에도 lock이 필요 없는 경우

다음과 같은 질문이 있군요.

스레드 동기화 관련
; http://lab.gamecodi.com/board/zboard.php?id=GAMECODILAB_QnA_etc&no=5292&z=

재현 코드를 만들어 보면, 다음과 같이 정리할 수 있습니다.

using System;
using System.Collections.Generic;
using System.Threading;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Program pg = new Program();
            pg.Start();

            Console.ReadLine();
        }

        Dictionary<int, string> _dict = new Dictionary<int, string>();

        private void Start()
        {
            _dict = MakeDict();

            Thread t1 = new Thread(readerFunc);
            t1.IsBackground = true;
            t1.Start();

            Thread t2 = new Thread(writerFunc);
            t2.IsBackground = true;
            t2.Start();
        }

        private void writerFunc()
        {
            while (true)
            {
                _dict = MakeDict();
            }
        }

        Dictionary<int, string> MakeDict()
        {
            Dictionary<int, string> dict = new Dictionary<int, string>();

            for (int i = 0; i < 1000000; i++)
            {
                dict.Add(i, i.ToString());
            }

            return dict;
        }

        private void readerFunc()
        {
            while (true)
            {
                var dict = _dict;
                int sum = 0;
                foreach (var item in dict)
                {
                    sum += item.Key;
                }

                if (sum != 1783293664)
                {
                    throw new ApplicationException("BROKEN-SYNC");
                }
            }
        }
    }
}

위와 같은 상황에서 _dict 변수에 대한 동기화가 필요할까요? ^^ (덧글들도 함께 읽어보시면 재미있습니다.)




당연하지만, 그래도 위의 코드를 실행해 보면 예외가 발생하지 않습니다. (물론 멀티 스레드의 문제에서 단순히 코드 수행으로 인한 예외가 발생하지 않았다고 해서 결점이 없다고 단정할 수는 없습니다. 마치 수학에서는 증명이 요구되듯이.)

이유는 간단합니다. 위의 경우에 _dict 멤버 변수는 참조형이기 때문에 4/8 바이트 포인터 변수이고 이는 CPU 워드 단위이므로 한 번에(atomic 하게) 값이 변하게 됩니다. 즉, _dict의 변숫값이 0x00112233이었는데 (32비트 CPU의) 쓰기 상황에서 4바이트 중 상위 2바이트 또는 하위 2바이트로 나눠서 쓰는 것이 아니므로 적어도 해당 멤버 변수가 가리키는 주소의 객체가 유효하다는 보장이 있습니다.

위와 같은 상황을 유형 별 시나리오로 생각해 보면 이해가 더 잘 될 수 있습니다.

가령 A 스레드에서 dict = _dict;를 수행 후 dict 변수를 통해 이전 컬렉션을 열람하는 사이 B 스레드에서 _dict를 업데이트하는 상황을 보겠습니다. 이 상황에서 A 스레드가 _dict에 대해 동기화 수단(이를테면, Monitor.Lock)을 수행하면 어떻게 될까요? 이런 경우 단지 B 스레드에서 _dict 변수에 값을 써넣는 시간만 지연될 뿐 A 스레드는 변함없이 이전 컬렉션의 데이터를 모두 열람하게 됩니다.

또는 A 스레드에서 dict = _dict;를 수행하기 전, B 스레드에서 _dict를 업데이트했다고 가정해 보겠습니다. 이 경우에도 역시 동기화 작업을 하는 유무에 상관없이 어차피 값이 잘 나옵니다.

굳이 문제가 된다면 컴파일러가 해당 변숫값이 메서드 안에서 읽히기만 한다고 판단해 최적화를 하는 경우입니다. 이런 경우를 위해 volatile 예약어 정도만 써주면 됩니다.

C++ volatile 키워드
; https://www.sysnet.pe.kr/2/0/413

C# - volatile 키워드로 인한 차이점을 발생시키는 예제
; https://www.sysnet.pe.kr/2/0/10850

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 6/27/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 16  17  18  19  20  21  [22]  23  24  25  26  27  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
13392정성태7/16/202310815오류 유형: 872. Oracle - ORA-01031: insufficient privileges
13391정성태7/14/202311197닷넷: 2132. C# - sealed 클래스의 메서드를 callback 호출했을 때 인라인 처리가 될까요?
13390정성태7/12/202311150스크립트: 53. 파이썬 - localhost 호출 시의 hang 현상
13389정성태7/5/202311503개발 환경 구성: 684. IIS Express로 호스팅하는 웹을 WSL 환경에서 접근하는 방법
13388정성태7/3/202311963오류 유형: 871. 윈도우 탐색기에서 열리지 않는 zip 파일 - The Compressed (zipped) Folder '[...].zip' is invalid. [1]파일 다운로드1
13387정성태6/28/202311163오류 유형: 870. _mysql - Commands out of sync; you can't run this command now
13386정성태6/27/202311904Linux: 61. docker - 원격 제어를 위한 TCP 바인딩 추가
13385정성태6/27/202311754Linux: 60. Linux - 외부에서의 접속을 허용하기 위한 TCP 포트 여는 방법
13384정성태6/26/202311223.NET Framework: 2131. C# - Source Generator로 해결하는 enum 박싱 문제파일 다운로드1
13383정성태6/26/202311009개발 환경 구성: 683. GPU 런타임을 사용하는 Colab 노트북 설정
13382정성태6/25/202310923.NET Framework: 2130. C# - Win32 API를 이용한 윈도우 계정 정보 (예: 마지막 로그온 시간)파일 다운로드1
13381정성태6/25/202311862오류 유형: 869. Fatal Python error: init_fs_encoding: failed to get the Python codec of the filesystem encoding
13380정성태6/24/202310341스크립트: 52. 파이썬 3.x에서의 동적 함수 추가
13379정성태6/23/202310789스크립트: 51. 파이썬 2.x에서의 동적 함수 추가
13378정성태6/22/202311050오류 유형: 868. docker - build 시 "CANCELED ..." 뜨는 문제
13377정성태6/22/202316025오류 유형: 867. 파이썬 mysqlclient 2.2.x 설치 시 "Specify MYSQLCLIENT_CFLAGS and MYSQLCLIENT_LDFLAGS env vars manually" 오류
13376정성태6/21/202311771.NET Framework: 2129. C# - Polly를 이용한 클라이언트 측의 요청 재시도파일 다운로드1
13375정성태6/20/202310766스크립트: 50. Transformers (신경망 언어모델 라이브러리) 강좌 - 2장 코드 실행 결과
13374정성태6/20/202310497오류 유형: 866. 파이썬 - <class 'AttributeError'> module 'flask.json' has no attribute 'JSONEncoder'
13373정성태6/19/202311883오류 유형: 865. 파이썬 - pymssql 설치 관련 오류 정리
13372정성태6/15/202310615개발 환경 구성: 682. SQL Server TLS 통신을 위해 사용되는 키 길이 확인 방법
13371정성태6/15/202311100개발 환경 구성: 681. openssl - 인증서 버전(V1 / V3)
13370정성태6/14/202311424개발 환경 구성: 680. C# - Ubuntu + Microsoft.Data.SqlClient + SQL Server 2008 R2 연결 방법 - TLS 1.2 지원
13369정성태6/13/202310639개발 환경 구성: 679. PyCharm(을 비롯해 JetBrains에 속한 여타) IDE에서 내부 Window들의 탭이 없어진 경우
13368정성태6/13/202311444개발 환경 구성: 678. openssl로 생성한 인증서를 SQL Server의 암호화 인증서로 설정하는 방법
13367정성태6/10/202312233오류 유형: 864. openssl로 만든 pfx 인증서를 Windows Server 2016 이하에서 등록 시 "The password you entered is incorrect" 오류 발생
... 16  17  18  19  20  21  [22]  23  24  25  26  27  28  29  30  ...