Microsoft MVP성태의 닷넷 이야기
.NET Framework: 1195. C# - Thread.Yield와 Thread.Sleep(0)의 차이점(?) [링크 복사], [링크+제목 복사],
조회: 7822
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 2개 있습니다.)
(시리즈 글이 4개 있습니다.)
.NET Framework: 914. C# - Task.Yield 사용법
; https://www.sysnet.pe.kr/2/0/12241

.NET Framework: 916. C# - Task.Yield 사용법 (2)
; https://www.sysnet.pe.kr/2/0/12245

.NET Framework: 1163.  C# - 윈도우 환경에서 usleep을 호출하는 방법
; https://www.sysnet.pe.kr/2/0/12980

.NET Framework: 1195. C# - Thread.Yield와 Thread.Sleep(0)의 차이점(?)
; https://www.sysnet.pe.kr/2/0/13033




C# - Thread.Yield와 Thread.Sleep(0)의 차이점(?)

예전 글에서,

(번역글) .NET Internals Cookbook Part 10 - Threads, Tasks, asynchronous code and others
67. Thread.Yield와 Thread.Sleep(0)의 차이점
; https://www.sysnet.pe.kr/2/0/11879#67

이에 대해 설명을 했는데, 그때 테스트가 잘못돼 다시 설명을 합니다. ^^;




자, 우선, Yield는 "현재 프로세서에 ready 상태의 스레드가 있는지 체크 후 있으면 해당 스레드로 전환이 되지만 없으면 현재 스레드가 계속 실행"하는 것을 테스트해보겠습니다.

이를 위해 다음과 같이 코드를 작성하고,

using System;
using System.Diagnostics;
using System.Numerics;
using System.Threading;

namespace ConsoleApp1
{
    class Program
    {
        static int _processorId = 1;

        static void Main(string[] args)
        {
            Thread t1 = new Thread(yieldProc);
            Thread t2 = new Thread(lowPriorityProc);
            Thread t3 = new Thread(lowPriorityProc);

            t3.Start();
            t2.Start();
            t1.Start();
            t1.Join();
            t2.Join();
        }

        private static void lowPriorityProc()
        {
            SetProcessAffinity(_processorId);

            Console.WriteLine("lowPriorityProc: " + AppDomain.GetCurrentThreadId());
            Thread.CurrentThread.Priority = ThreadPriority.Lowest;

            int i = 0;
            BigInteger sum = new BigInteger();

            while (true)
            {
                i++;
                sum += i;
            }
        }

        private static void yieldProc()
        {
            SetProcessAffinity(_processorId);

            Console.WriteLine("yieldProc: " + AppDomain.GetCurrentThreadId());
            while (true)
            {
                Thread.Yield();
            }
        }

        // https://www.sysnet.pe.kr/2/0/10933
        static void SetProcessAffinity(int cpuNumber)
        {
            if (cpuNumber >= Environment.ProcessorCount)
            {
                cpuNumber = 0;
            }

            foreach (ProcessThread pthread in Process.GetCurrentProcess().Threads)
            {
                if (pthread.Id == AppDomain.GetCurrentThreadId()) // .NET Framework에서만!
                {
                    pthread.ProcessorAffinity = new IntPtr(1 << cpuNumber);
                    break;
                }
            }
        }
    }
}

실행하면 이런 결과가 나올 텐데요,

lowPriorityProc: 65544
lowPriorityProc: 21836
yieldProc: 45984

이때 Process Explorer를 이용해 "yieldProc"으로 지정된 45984 스레드를 찾아보면 CPU 값이 (24 코어에서) "0.01" 정도로 나오는 것을 확인할 수 있습니다. 왜냐하면, 같은 CPU에 실행 중인 lowPriorityProc 스레드 2개가 더 있기 때문에 Yield는 그 스레드로 계속해서 실행을 양보하기 때문에 (1/n도 아닌 더욱) 낮은 CPU 사용량만을 보이는 것입니다. (또한, 위의 코드에서 lowPriorityProc의 우선순위가 ThreadPriority.Lowest로 설정되었는데도 CPU 양보를 하고 있다는 것을 알 수 있습니다.)

반면, lowPriorityProc에서 SetProcessAffinity 호출을 제거하면,

private static void lowPriorityProc()
{
    // SetProcessAffinity(_processorId);

    Console.WriteLine("lowPriorityProc: " + AppDomain.GetCurrentThreadId());
    Thread.CurrentThread.Priority = ThreadPriority.Lowest;

    // ...[생략]...
}

이제는 yieldProc 스레드가 실행 중인 CPU를 lowPriorityProc 스레드에서 점유하지 않으므로, 이제 yieldProc은 CPU 100% 현상을 보이게 됩니다. 위에서 설명한 그대로 현상이 재현된 것입니다.

자, 그럼 Sleep(0)을 검증해 볼까요?

다시 위의 첫 번째 예제에서 단지 yieldProc의 내부만 Sleep 호출로 바꾼 후,

private static void yieldProc()
{
    SetProcessAffinity(_processorId);

    Console.WriteLine("yieldProc: " + AppDomain.GetCurrentThreadId());
    while (true)
    {
        // Thread.Yield();
        Thread.Sleep(0);
    }
}

실행하면, Process Explorer에서 yieldProc의 스레드는 lowPriorityProc의 스레드보다 우선순위가 높게 설정돼 있으므로 양보를 하지 말아야 합니다. 하지만, 실제로 실행해 보면 Yield처럼 낮은 CPU 사용량을 보입니다. 즉, 양보를 하고 있는 것입니다.

역시나, 이번에도 테스트상으로는 Yield와 Sleep(0)의 차이점을 알 수가 없습니다. ^^;

결국, Yield와 Sleep(0)은 경우에 따라 CPU 사용량이 (같은 CPU를 사용하는 다른 스레드가 있다면 1/n보다) 낮을 수도 있고, (같은 CPU를 사용하는 다른 스레드가 없다면) 높을 수도 있습니다. 또한 Yield/Sleep 모두, (스레드의 우선순위에 상관없이) 같은 CPU를 사용하는 다른 스레드가 있다면 1/n 사용량을 확보하진 못합니다.




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 4/18/2022]

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

비밀번호

댓글 작성자
 




... 61  62  63  64  65  66  67  68  69  70  71  72  [73]  74  75  ...
NoWriterDateCnt.TitleFile(s)
11822정성태2/20/201911638오류 유형: 517. docker에 설치한 MongoDB 서버로 연결이 안 되는 경우
11821정성태2/20/201912096오류 유형: 516. Visual Studio 2019 - This extension uses deprecated APIs and is at risk of not functioning in a future VS update. [1]
11820정성태2/20/201915237오류 유형: 515. 윈도우 10 1809 업데이트 후 "User Profiles Service" 1534 경고 발생
11819정성태2/20/201913908Windows: 158. 컴퓨터와 사용자의 SID(security identifier) 확인 방법
11818정성태2/20/201912836VS.NET IDE: 131. Visual Studio 2019 Preview의 닷넷 프로젝트 빌드가 20초 이상 걸리는 경우 [2]
11817정성태2/17/20199988오류 유형: 514. WinDbg Preview 실행 오류 - Error : DbgX.dll : WindowsDebugger.WindowsDebuggerException: Could not load dbgeng.dll
11816정성태2/17/201912492Windows: 157. 윈도우 스토어 앱(Microsoft Store App)을 명령행에서 직접 실행하는 방법
11815정성태2/14/201911385오류 유형: 513. Visual Studio 2019 - VSIX 설치 시 "The extension cannot be installed to this product due to prerequisites that cannot be resolved." 오류 발생
11814정성태2/12/201910203오류 유형: 512. VM(가상 머신)의 NT 서비스들이 자동 시작되지 않는 문제
11813정성태2/12/201911809.NET Framework: 809. C# - ("Save File Dialog" 등의) 대화 창에 확장 속성을 보이는 방법
11812정성태2/11/20199631오류 유형: 511. Windows Server 2003 VM 부팅 후 로그인 시점에 0xC0000005 BSOD 발생
11811정성태2/11/201913418오류 유형: 510. 서버 운영체제에 NVIDIA GeForce Experience 실행 시 wlanapi.dll 누락 문제
11810정성태2/11/201911485.NET Framework: 808. .NET Profiler - GAC 모듈에서 GAC 비-등록 모듈을 참조하는 경우의 문제
11809정성태2/11/201912995.NET Framework: 807. ClrMD를 이용해 메모리 덤프 파일로부터 특정 인스턴스를 참조하고 있는 소유자 확인
11808정성태2/8/201914051디버깅 기술: 123. windbg - 닷넷 응용 프로그램의 메모리 누수 분석
11807정성태1/29/201912360Windows: 156. 가상 디스크의 용량을 복구 파티션으로 인해 늘리지 못하는 경우 [4]
11806정성태1/29/201912111디버깅 기술: 122. windbg - 덤프 파일로부터 PID와 환경 변수 등의 정보를 구하는 방법
11805정성태1/28/201913959.NET Framework: 806. C# - int []와 object []의 차이로 이해하는 제네릭의 필요성 [4]파일 다운로드1
11804정성태1/24/201912008Windows: 155. diskpart - remove letter 이후 재부팅 시 다시 드라이브 문자가 할당되는 경우
11803정성태1/10/201911488디버깅 기술: 121. windbg - 닷넷 Finalizer 스레드가 멈춰있는 현상
11802정성태1/7/201912874.NET Framework: 805. 두 개의 윈도우를 각각 실행하는 방법(Windows Forms, WPF)파일 다운로드1
11801정성태1/1/201913912개발 환경 구성: 427. Netsh의 네트워크 모니터링 기능 [3]
11800정성태12/28/201813195오류 유형: 509. WCF 호출 오류 메시지 - System.ServiceModel.CommunicationException: Internal Server Error
11799정성태12/19/201813993.NET Framework: 804. WPF(또는 WinForm)에서 UWP UI 구성 요소 사용하는 방법 [3]파일 다운로드1
11798정성태12/19/201813229개발 환경 구성: 426. vcpkg - "Building vcpkg.exe failed. Please ensure you have installed Visual Studio with the Desktop C++ workload and the Windows SDK for Desktop C++"
11797정성태12/19/201810582개발 환경 구성: 425. vcpkg - CMake Error: Problem with archive_write_header(): Can't create '' 빌드 오류
... 61  62  63  64  65  66  67  68  69  70  71  72  [73]  74  75  ...