Programming

이 코드는 릴리스 모드에서 정지하지만 디버그 모드에서는 제대로 작동합니다.

procodes 2020. 8. 3. 09:03
반응형

이 코드는 릴리스 모드에서 정지하지만 디버그 모드에서는 제대로 작동합니다.


나는 이것을 보았고 디버그 및 릴리스 모드 에서이 동작의 이유를 알고 싶습니다.

public static void Main(string[] args)
{            
   bool isComplete = false;

   var t = new Thread(() =>
   {
       int i = 0;

        while (!isComplete) i += 0;
   });

   t.Start();

   Thread.Sleep(500);
   isComplete = true;
   t.Join();
   Console.WriteLine("complete!");
}

isComplete변수 에 '휘발성'키워드가 없기 때문에 최적화 프로그램이 바보라고 생각 합니다.

물론 지역 변수이기 때문에 추가 할 수 없습니다. 물론 지역 변수이므로 지역 변수가 스택에 유지되고 자연스럽게 항상 "신선한" 상태이기 때문에 전혀 필요하지 않습니다 .

그러나 컴파일 후에는 더 이상 지역 변수아닙니다 . 익명 대리자로 액세스되므로 코드가 분할되고 다음과 같은 도우미 클래스 및 멤버 필드로 변환됩니다.

public static void Main(string[] args)
{
    TheHelper hlp = new TheHelper();

    var t = new Thread(hlp.Body);

    t.Start();

    Thread.Sleep(500);
    hlp.isComplete = true;
    t.Join();
    Console.WriteLine("complete!");
}

private class TheHelper
{
    public bool isComplete = false;

    public void Body()
    {
        int i = 0;

        while (!isComplete) i += 0;
    }
}

TheHelper클래스를 처리 할 때 멀티 스레드 환경의 JIT 컴파일러 / 최적화 프로그램 이 실제로 메소드 시작시 일부 레지스터 또는 스택 프레임 의 값 캐시 할 수 있으며 메소드가 끝날 때까지 값을 새로 고치지 않을 수 있습니다. "= true"가 실행되기 전에 thread & method가 종료되지 않는다는 보장이 없기 때문에, 보장이 없으면 왜 캐시하지 않고 매번 읽지 않고 힙 객체를 읽는 성능을 향상 시키는가 되풀이.falseBody()

이것이 바로 키워드 volatile가 존재 하는 이유 입니다.

이 헬퍼 클래스가되기 위해서는 올바른 작은 조금 더 비트 1) 멀티 스레드 환경에서, 그것은이 있어야합니다

    public volatile bool isComplete = false;

but, of course, since it's autogenerated code, you can't add it. A better approach would be to add some lock()s around reads and writes to isCompleted, or to use some other ready-to-use synchronization or threading/tasking utilities instead of trying to do it bare-metal (which it will not be bare-metal, since it's C# on CLR with GC, JIT and (..)).

The difference in debug mode occurs probably because in debug mode many optimisations are excluded, so you can, well, debug the code you see on the screen. Therefore while (!isComplete) is not optimized so you can set a breakpoint there, and therefore isComplete is not aggressively cached in a register or stack at the method start and is read from the object on the heap at every loop iteration.

BTW. That's just my guesses on that. I didn't even try to compile it.

BTW. It doesn't seem to be a bug; it's more like a very obscure side effect. Also, if I'm right about it, then it may be a language deficiency - C# should allow to place 'volatile' keyword on local variables that are captured and promoted to member fields in the closures.

1) see below for a comments from Eric Lippert about volatile and/or this very interesting article showing the levels of complexity involved in ensuring that code relying on volatile is safe ..uh, good ..uh, let's say OK.


The answer of quetzalcoatl is correct. To shed more light on it:

The C# compiler and CLR jitter are permitted to make a great many optimizations that assume that the current thread is the only thread running. If those optimizations make the program incorrect in a world where the current thread is not the only thread running that is your problem. You are required to write multithreaded programs that tell the compiler and jitter what crazy multithreaded stuff you are doing.

In this particular case the jitter is permitted -- but not required -- to observe that the variable is unchanged by the loop body and to therefore conclude that -- since by assumption this is the only thread running -- the variable will never change. If it never changes then the variable needs to be checked for truth once, not every time through the loop. And this is in fact what is happening.

How to solve this? Don't write multithreaded programs. Multithreading is incredibly hard to get right, even for experts. If you must, then use the highest level mechanisms to achieve your goal. The solution here is not to make the variable volatile. The solution here is to write a cancellable task and use the Task Parallel Library cancellation mechanism. Let the TPL worry about getting the threading logic right and the cancellation properly send across threads.


I attached to the running process and found (if I didn't make mistakes, I'm not very practiced with this) that the Thread method is translated to this:

debug051:02DE04EB loc_2DE04EB:                            
debug051:02DE04EB test    eax, eax
debug051:02DE04ED jz      short loc_2DE04EB
debug051:02DE04EF pop     ebp
debug051:02DE04F0 retn

eax (which contains the value of isComplete) is loaded first time and never refreshed.


Not really an answer, but to shed some more light on the issue:

The problem seems to be when i is declared inside the lambda body and it's only read in the assignment expression. Otherwise, the code works well in release mode:

  1. i declared outside the lambda body:

    int i = 0; // Declared outside the lambda body
    
    var t = new Thread(() =>
    {
        while (!isComplete) { i += 0; }
    }); // Completes in release mode
    
  2. i is not read in the assignment expression:

    var t = new Thread(() =>
    {
        int i = 0;
        while (!isComplete) { i = 0; }
    }); // Completes in release mode
    
  3. i is also read somewhere else:

    var t = new Thread(() =>
    {
        int i = 0;
        while (!isComplete) { Console.WriteLine(i); i += 0; }
    }); // Completes in release mode
    

My bet is some compiler or JIT optimization regarding i is messing up things. Somebody smarter than me will probably be able to shed more light on the issue.

Nonetheless, I wouldn't worry too much about it, because I fail to see where similar code would actually serve any purpose.

참고URL : https://stackoverflow.com/questions/41632387/this-code-hangs-in-release-mode-but-works-fine-in-debug-mode

반응형