소프트웨어/Thread

멀티스레딩 교재(Multithreading Tutorial)

카켈 2007. 4. 30. 14:03
원본 주소 : http://www.computersciencelab.com/MultithreadingTut1.htm
예제 코드 :


저작권 표기 : 원 저작글은 computersciencelab.com 의 John Kopplin 님에게 있습니다. 번역에 대한 저작권은 딱히 존재 하지 않지만 퍼 가실때 1차와 2차(http://cakel.tistory.com) 출처를 표기 해주셨으면 합니다. 이 글은 교육용입니다. 배우는 입장이기 때문에 오탈자가 상당수 있습니다. 덧글이나 아래 있는 메일 주소로 이야기 해주시면 최대한 수정하도록 하겠습니다.

Copyright notice : This article is owned to John Koppin at computersciencelab.com and I don't claim any Korean translation copyrights, but if you scrap or copy, please note this site. If you look any errata, comment or mail below address. This article is study purporse only.

Multithreading Tutorial
멀티 스레딩 교재


Background
배경

When you run two programs on an operating system that offers memory protection, as Windows and UNIX/LINUX do, the two programs are executed as separate processes which means they are given separate address spaces. This means that when program #1 modifies the address 0x800A 1234 in its memory space, program #2 does NOT see any change in the contents of its memory at address 0x800A 1234. With simpler operating systems that cannot accomplish this separation of processes, a faulty program can bring down not only itself but the other programs running on that computer (including the operating system itself).

  두개의 프로그램을 윈도우즈나 리눅스/유닉스가 하는 메모리 보호를 하는 운영체제에서 동시에 쓸때, 그 프로그램들은 독립적인 메모리 주소를 가진 프로세스로 동작하게 됩니다. 이는 프로그램 #1이 0x800A 1234 에서 수정하고 있다면 프로그램 #2 는 0x800A 1234 주소가 변경 되었는지 알수가 없습니다. 단순한 운영체제에서는 이 프로세스를 구별 하지 못하여 잘못 만들어진 프로그램은 자신 뿐 아니라 (운영체제를 포함한) 다른 프로그램들도 못쓰게(날리게) 만들수 있습니다.

The ability to execute more than one process at a time is known as multi-processing. A process consists of a program (usually called the application) whose statements are performed in an independent memory area. There is a program counter that remembers which statement should be executed next and there is a stack which holds the arguments passed to functions as well as the variables local to functions and there is a heap which holds the remaining memory requirements of the program. The heap is used for the memory allocations that must persist longer than the lifetime of a single function. In the C language you use malloc to acquire memory from the heap and in C++ you use the new keyword.

한번에 여러 개의 프로세스를 한번에 수행하는 능력을 다중(멀티)-프로세싱이라고 알려져 있습니다. 한 프로세스는 독립적인 메모리 공간을 가진 프로그램를 이루고 있습니다.(보통 응용프로그램-어플리케이션이라고 합니다.) 다음 수행할 문장이 어디에 있을지 저장하는 곳인 프로그램 카운터가 존재하고, 로컬 변수가 위치하고 함수의 인수를 보관하는 스택이 있으며, 프로그램이 요구한 용량을 쓰고 남은 공간인 영역이 있습니다. 힙은 단일 함수의 수명 보다도 더 오랫동안 존재 해야 하는 메모리를 할당 받는데 사용합니다. C 언어에서는 malloc 함수로 힙에서 메모리를 얻고 C++ 에서는 new 키워드로 얻습니다.

Sometimes it is useful to arrange for two or more processes to work together to accomplish one goal. One situation where this is beneficial is where the computer's hardware offers multiple processors. In the old days this meant two sockets on the motherboard each populated with an expensive Xeon chip. Thanks to advances in VLSI integration, these two processor chips can now fit in a single package. Examples are Intel's "Core Duo" and AMD's "Athlon 64 X2". If you want to keep two microprocessors busy working on a single goal, you basically have to two choices:

가끔 두개 이상의 프로세스가 같이 일을 하여 목적을 이루는 것이 더 유용할 때가 있습니다. 예를 들면 여러 프로세서를 동시에 쓸수 있는 하드웨어가 그러하겠습니다. 예전에 이 이야기는 두개의 CPU 슬롯을 가진 메인보드에 존재했던 비싼 인텔 제온(Xeon) CPU칩이 동작하는 시스템에 관한 이야기였습니다. 하지만 VLSI 집적회로의 진보 덕에 요즘은 하나의 팩키지에 두개(이상)의 프로세서가 내장 될수 있습니다. 인텔의 코어 듀오(Intel Core Duo), AMD 의 애슬론(Athlon) 64 X2 가 대표적 예가 되겠네요. 당신은 두 마이크로프로세서를 바쁘게 작동하여 하나의 목적을 달성하기 위해서, 기본적으로 두가지 선택을 해야 합니다.
  1. design your program to use multiple processes (which usually means multiple programs), or
    프로그램을 다중프로세스를 사용할수 있게 설계 (보통 멀티 프로그램을 의미합니다.) 또는

  2. design your program to use multiple threads
    멀티 스레드를 쓰는 프로그램을 설계 합니다.

So what's a thread? A thread is another mechanism for splitting the workload into separate execution streams. A thread is lighter weight than a process. This means it offers less flexibility than a full blown process, but can be initiated faster because there is less for the operating system to set up. What's missing? The separate address space is what is missing. When a program consists of two or more threads, all the threads share a single memory space. If one thread modifies the contents of address 0x800A 1234, then all the other threads immediately see a change in the contents of their address 0x800A 1234. Furthermore, all the threads share a single heap. If one thread allocates (via malloc or new) all of the memory available in the heap, then attempts at additional allocations by the other threads will fail.

스레드가 무엇알까요? 스레드란 작업량을 별개의 실행 스트림으로 분리한 또 다른 메카니즘(작동구현방식)입니다. 스레드 프로세스보다 가볍습니다. 이는 전체 프로세스 보다 덜 유연하다는 의미가 되지만 운영체제가 실행을 준비하는데 덜 부담을 주기 때문에 더 빠르게 작동될수 있습니다. 뭔가 빠졌죠? 각각 구별된 주소공간이 빠진 것입니다. 하나의 스레드가 0x800A 1234 의 공간을 변경하면 다른 모든 스레드가 동시에 0X800A 1234 주소값이 변경 된것을 볼수 있습니다. 더 나아가, 모든 스레드가 단일 힙을 공유 합니다. 만약 하나의 스레드(malloc 이나 new 를 통해)가 가용 할수 있는 모든 메모리 공간을 점유 한다면, 다른 스레드가 추가 할당할려는 시도는  실패 할 것입니다.

But each thread is given its own stack. This means thread #1 can be calling FunctionWhichComputesALot() at the same time that thread #2 is calling FunctionWhichDrawsOnTheScreen(). Both of these functions were written in the same program. There is only one program. But there are independent threads of execution running through that program.

그러나 각 스레드는 그만의 스택을 가지고 있습니다. 이는 스레드 #1 이 FunctionWhichComputesALot() 이라는 함수를 부를때 FunctionWhichDrawsOnTheScreen() 이라는 함수를 호출 합니다. 이 둘 함수는 같은 프로그램 내에서 작성된 것입니다. 하지만 그 프로그램에서 수행하는 각각의 독립적인 수행 스레드들이 존재 합니다.

What's the advantage? Well, if your computer's hardware offers two processors, then two threads can run simultaneously. And even on a uni-processor, multi-threading can offer an advantage. Most programs can't perform very many statements before they need to access the hard disk. This is a very slow operation and hence the operating system puts the program to sleep during the wait. In fact, the operating system assigns the computer's hardware resources to somebody else's program during the wait. But if you have written a multi-threaded program, then when one of your threads stalls, your other threads can continue.

여기서 얻는 이익은 무엇일까요? 음... 만약, 컴퓨터 하드웨어가 두개의 프로세서를 제공하고 있다면  두개의 스레드가 동시에 동작할 수 있습니다. 그리고 단일 프로세서라도 멀티-스레딩으로 같은 이득을 제공 받을수 있습니다. 대부분 프로그램은 하드 디스크에 접근 할 필요가 있기 전까지는 아주 많은 명령을 수행할 수 없습니다. 이는 아주 느린 작업이고 따라서 운영체제는 프로그램을 기다리는 동안 중단(sleep) 시킵니다. 사실 운영체제는 컴퓨터 하드웨어를 다른 사용자의 프로그램이 기다리는 동안에 하드웨어 자원을 할당 합니다. 하지만 멀티-스레드 프로그램을 쓰는데, 스레드들 중 하나가 멈춘다 해도, 다른 스레드는 계속 동작 할수 있습니다.

The Jaeschke Magazine Articles
재스케 매거진 기사

One good way to learn any new programming concept is to study other people's code. You can find source code in magazine articles and posted on the Internet at sites such as codeproject.com . I came across some good examples of multi-threaded programs in two articles written for the C/C++ Users Journal by Rex Jaeschke. In the October 2005 issue Jaeschke wrote an article entitled "C++/CLI Threading: Part 1" and in the November 2005 issue he wrote his follow-up article entitled "C++/CLI Threading: Part 2". Unfortunately the C/C++ Users Journal magazine folded shortly after these articles appeared. But the original articles and Jaeschke's source code are still available at the following web sites:

다른 사람의 코드를 보면서 새로운 프로그래밍의 계념을 익히는 건 좋은 방법들 중 하나 입니다. codeproject.com 같은 인터넷에 게시된 글이나 기사에 쓰인 소스 코드를 찾을수 있을 겁니다. 전 렉스 재스케에서 발간하는 C/C++ 사용자 저널에서 본  멀티-스레드에 관한 두가지의 좋은 예제 코드를 접할수 있었습니다. 2005년 10원에 발간된 이 재스케 잡지 기사의 제목이 "C++/CLI 스레딩 : 1부" 그리고 2005년 11월에 이어서 발간한 "C++/CLI 스레딩 : 파트2" 입니다. 불행히도 C/C++ 사용자 저널 매거진은 이들 기사가 나온지 얼마 안되어서 절간 되었는데요, 하지만 아직 원글과 재스케의 소스 코드가 아래 웹 사이트에서 받을수 있습니다.

Part 1: http://www.ddj.com/dept/windows/184402018
Part 2: http://www.ddj.com/dept/windows/184402029

You'll notice that the content from the defunct C/C++ Users Journal has been integrated into the Dr. Dobb's Portal web site, which is associated with Dr. Dobb's Journal, another excellent programming magazine.

당신은 더 이상 볼수 없을거 같은 컨텐츠를 다른 휼륭한 프로그래밍 매거진인 Dr, Dobb's 의 저널의 포털 사이트에서 위 컨텐츠를 보시게 될텐데요, 이는 재스케가 그 사이트로 병합되어서 그렇습니다.

You might not be familiar with the notation C++/CLI. This stands for "C++ Common Language Infrastructure" and is a Microsoft invention. You're probably familiar with Java and C#, which are two languages that offer managed code where the operating system rather than the programmer is responsible for deallocating all memory allocations made from the heap. C++/CLI is Microsoft's proposal to add managed code to the C++ language.

C++/CLI 에 친숙하지 않을지도 모르겠네요. 이는 C++ 공용 언어 기반공간(C++ 커먼 랭귀지 인프라스트럭쳐) 의 약자로써 마이크로소프트가 창안했습니다. 아마 자바나 C# 같은 운영체제 보다 프로그래머가 힙에서 만들어진 메모리를 제거하는 것에 책임을 지고, 제공된 관리 코드를 사용하는 언어에 친숙 할 것입니다. C++/CLI 는 마이크로소프트가 C++ 언어에 관리하는 코드를 추가해서 제안한 것입니다.

I am not a fan of this approach so I wasn't very interested in Jaeschke's original source code. I am sure Java and C# are going to hang around but C++/CLI attempts to add so many new notations (and concepts) on top of C++, which is already a very complicated language, that I think this language will disappear.

저는 이 부분에 관심이 없어서 그런지 재스케의 원래 소스 코드에 크게 관심이 없습니다. 전 Java 나 C# 이가 이미 활성화 되어 있지만 C++/CLI 는 아주 복잡해진 새로운 상위 개념을 넣었기 때문에 전   언어가 사라질 거라 생각하고 있습니다.

But I still read the original C/C++ Users Journal article and thought Jaeschke had selected good examples of multi-threading. I especially liked how his example programs were short and yet displayed data corruption when run without the synchronization methods that are required for successful communication between threads. So I sat down and rewrote his programs in standard C++. This is what I am sharing with you now. The source code I present could also be written in standard C. In fact, that's easier than accomplishing it in C++ for a reason we will get to in just a minute.

하지만 전 아직 원 C/C++ 사용자 저널 기사를 읽고 있고 재스케가 멀티스레드에 관한 좋은 예제를 골랐다고 생각하고 있습니다. 전 특별히 짧고 스레드간의 필요한 동기화 방법이 요구하지 않고도 데이터가 문제 없이 표시되는 그의 예제를 좋아 합니다. 그래서 전 마음 먹고 그의 프로그램을 표준 C++ 언어로 다시 썼습니다. 이게 지금 제가 공유하고 있는 것입니다. 이 소스 코드는 다시 C 언어로 쓸수 있습니다. 사실은 C++ 로 완수 하는 것 보다 C 로 만드는게 쉽습니다. 그 이유는 좀 있다가 알려 드리죠.

This is probably the right time to read Jaeschke's original articles, since I don't plan to repeat his great explanations of multitasking, reentrancy, atomicity, etc. For example, I don't plan to explain how a program is given its first thread automatically and all additional threads must be created by explicit actions by the program (oops). The URLs where you can find Jaeschke's two articles are given above.

이것이 재스케의 원 기사를 제대로 보는 기회가 되겠습니다. 왜냐하면 전 그의 멀티태스킹, 리엔트런시(데이터의 변조 없이 수행하는 함수), 원자성(더 이상 파괴될수 없는 성질), 등에 관한 대단한 설명에 대해 다시 언급 하지 않을 테니깐요. 예를 들어 그의 프로그램에서 어떻게 첫번째 스레드가 자동으로 주어지는지 추가적 스레드가 프로그램에 의해 명백하게 만들어져야만 한다는 것을 (이런... 알려 드렸네;) 알려 드리지 않을 것입니다. 저 URL들이 재스케의 두가지 기사 방금 언급한 것에 대한 것에 대한 해답을 찾을 것입니다.

Creating Threads Under Windows
스레드를 윈도우즈 하에서 생성하기

It is unfortunate that the C++ language didn't standardize the method for creating threads. Therefore various compiler vendors invented their own solutions. If you are writing a program to run under Windows then you will want to use the WIN32 API to create your threads. This is what I will demonstrate. The WIN32 API offers the following function to create a new thread:

불행히도 C++ 언어에서는 스레드를 생성하는 것에 대한 표준화가 이루어 지지 않았습니다. 그래서 다양한 컴파일러에서 그들만의 사용책을 제시하고 있는데요, 만약 사용자가 윈도우즈 하에서 프로그램을 돌리고 싶으시다면 WIN32 API로 스레드를 만들어하고 싶어 할 것입니다. 이게 제가 보여 드리고 싶은 것입니다. WIN32 API 는 아래 함수로 새 스레드를 생성하게 해줍니다.
 
uintptr_t _beginthread( 

    void( __cdecl *start_address )( void * ), unsigned stack_size, void *arglist 


);
 

This function signature might look intimidating, but using it is easy. The _beginthread() function takes 3 passed parameters. The first is the name of the function which you want the new thread to begin executing. This is called the thread's entry-point-function. You get to write this function, and the only requirements are that it take a single passed parameter (of type void*) and that it returns nothing. That is what is meant by the function signature:

이 함수 서명자는 웬지 보면 무섭습니다 하지만 사용한건 쉬워요. _beginthread() 함수는 3가지 인수를 가집니다. 첫번째 함수의 인자는 새로운 스레드가 시작되기를 원하는 함수의 이름입니다. 이를 가리켜 스레드의 진입-점-함수(엔트리-포인트-함수)라 불립니다. 프로그래머는 이 함수를 쓰되 그 함수는 void* 형식을 인수를 가지는 함수여야 합니다, 그리고 아무 것도 되돌리지(리턴) 않구요. 이 것은 함수 서명자로 의미하는 바입니다.
 
    void( __cdecl *start_address )( void * ),
 

The second passed parameter to the _beginthread() function is a requested stack size for the new thread (remember, each thread gets its own stack). However I always set this parameter to 0 which forces the Windows operating system to select the stack size for me, and I haven't had any problems with this approach. The final passed parameter to the _beginthread() function is the single parameter you want passed to the entry-point-function. This will be made clear by the following example program.

 _beginthread() 함수의 두번째 파라메터(인수)는 새 스레드가 필요한 스택 메모리의 크기 입니다.(각각의 스레드는 그만의 스택이 필요하다는 사실을 잊지 마세요). 하지만 전 항상 그 파라메터 값을 0으로 맟추는데, 이렇게 하면 윈도우즈 운영체제가 저를 위해 자동으로 선택하라고 설정합니다. 이걸로 전 어떤 문제를 받지 않았습니다. _beginthread() 함수의 세번째 인수는 단일 진입-점-함수에 넣고 싶은 단일 파라메터(인수)값입니다. 다음 예제에서 확실해질 것입니다.
 
#include <stdio.h>
#include <windows.h>
#include <process.h>         // needed for _beginthread()- 필요합니다.
 
void  silly( void * );       // function prototype 함수의 프로토 타입

int main()
{
    // Our program's first thread starts in the main() function.
    // 우리의 프로그램의 첫번째 스레드는 main() 함수에서 시작 할 겁니다. 

    printf( "Now in the main() function.\n" );

    // Let's now create our second thread and ask it to start
    // in the silly() function.
    // 우리의 두번째 스레드가 생성하고, silly() 함수 내에서 그 스레드에서
    // 시작할지 묻습니다. 

    _beginthread( silly, 0, (void*)12 );

    // From here on there are two separate threads executing
    // our one program.
    // 여기서 부터는 두개의 스레드가 한 프로그램에서 동작하게 됩니다. 

    // This main thread can call the silly() function if it wants to.
    // 함수가 원한다면 메인 스레드에서 silly()를 부를 수 있습니다.  

    silly( (void*)-5 );

    Sleep( 100 );
}

void  silly( void *arg )
{
    printf( "The silly() function was passed %d\n", (INT_PTR)arg ) ;
}
 

Go ahead and compile this program. Simply request a WIN32 Console Program from Visual C++ .NET 2003's New Project Wizard and then "Add a New item" which is a C++ source file (.CPP file) in which you place the statements I have shown. I am providing Visual C++ .NET 2003 workspaces for Jaeschke's (modified) programs but you need to know the key to starting a multi-threaded program from scratch: you must remember to perform one modification to the default project properties that the New Project Wizard gives you. Namely, you must open up the Project Properties dialog (select "Project" from the main Visual C++ menu and then select "Properties").

  지금 이 프로그램을 컴파일해보세요. 간단하게 비주얼 C++ .NET 2003 에서 Win32 콘솔 프로그램을 요구하여 새 프로젝트 마법사를 여시고, 제가 보여준 코드를 넣을수  있는 C++ 소스(.CPP)를 추가하는 "새 항목 추가"를 고르세요. 전 재스케의 (수정된) 프로그램을 위한 비주얼 C++ .NET 2003 작업공간(워크스페이스)를 제공합니다. 하지만 다중(멀티)-스레드 프로그래밍을 시작 할때 한가지 알아야 할 기본을 알아 두셔야 할 필요가 있습니다 : 당신은 반드시 새 프로젝트 마법사가 제공하는 기본 프로젝트 설정에서 하나를 변경해야 합니다. 다시 말해 프로젝트 설정 대화상자(비주얼 C++ 메뉴에서 "프로젝트 - 등록정보" 을 눌러 주세요)

In the left hand column of this dialog you will see a tree view control named "Configuration Properties" with the main sub-nodes labeled "C/C++", "Linker", etc. Double-click on the "C/C++" node to open this entry up. Then click on "Code Generation". In the right hand area of the Project Properties dialog you will now see listed "Runtime Library". This defaults to "Single Threaded Debug (/MLd)". [The notation /MLd indicates that this choice can be accomplished from the compiler command line using the /MLd switch.] You need to click on this entry to observe a drop-down list control where you must select Multi-threaded Debug (/MTd). If you forget to do this, your program won't compile and the error message will complain about the _beginthread() identifier.

대화 상자의 왼쪽 열에서 사용자는 "설정 등록정보" 제어 트리뷰(메뉴를 나무 가지 처럼 볼수 있게 해둔 형태)에서 메인 서브-항목(노드)에 "C/C++", "Linker", 등이라 적혀 있는 것을 볼텐데요. "C/C++" 을 더블클릭하세면 새부 항목이 열립니다. 항목에 보시면 "코드 생성(Code Generation)"이라는 항목이 있습니다. 오른쪽 영역의 프로젝트 등록정보에서 당신은 "런타임 라이브러리"(실행중 참고항목, Runtime Library)". 목록을 보게 될 것입니다. 기본 설정으로 "단일 스레드형의 디버그(/MLd)", [/MLd 라고 적힌 것은 컴파일러가 /MLd 스위치를 써서 컴파일을 완수 할 것이라고 언급 하는 것입니다]. 당신은 아래로-내림(드롭-다운)메뉴를 눌러, 반드시 다중(멀티)-스레디형의 디버그(/MTd). 라고 선택 하셔야 합니다. 이걸 까먹는다면 프로그램은 컴파일 되지 않고 _beginthread() 식별자에 대한 오류 메시지로 짜증 낼 겁니다.

A very interesting thing happens if you comment out the call to the Sleep() function seen in this example program. Without the Sleep() statement the program's output will probably only show a single call to the silly() function with the passed argument -5. This is because the program's process terminates as soon as the main thread reaches the end of the main() function and this may occur before the operating system has had the opportunity to create the other thread for this process.

아주 흥미로운 것은 이 예제에 보여진 Sleep() 함수가 어떻게 될아갈지에 관한 것입니다. Sleep() 함수 없이 프로그램의 출력은 아마 인수가 5씩 감소된 단일(1번) silly() 함수가 호출 될 것입니다. 이는 아마도 프로그램의 프로세스가 메인 스레드에서 생성하는 추가 스레드가 운영체제에서 생성하기 전에 이미 main() 함수의 끝에 다다라서 생성할 기회도 얻기 전에 종료 되어서 그럴 것입니다.

This is one of the discrepancies from what Jaeschke says concerning C++/CLI. Evidently in C++/CLI each thread has an independent lifetime and the overall process (which is the container for all the threads) persists until the last thread has decided to die. Not so for straight C++ WIN32 programs: the process dies when the primary thread (the one that started in the main function) dies. The death of this thread means the death of all the other threads.

재스케는 이 어긋난 프로그램에 대한 설명으로 C++/CLI 에 관하여 이야기 합니다. 결국 C++/CLI 에 있는 각 쓰래드는 독립적인 실행 시간을 가지고 있고 마지막에 남은 프로세서가 끝나기를 결정할때 까지 존재하는 전반적인 프로세스를 가지고 있다는 이야기가 됩니다.(다시 말해 모든 스레드를 가지는 컨테이너 - 담는 저장공간). C++ WIN32 프로그램에만 국한된 것이 아닙니다 : 첫 프로세스(메인 함수에서 시작하는 스레드)가 끝나 버리면 프로세스가 끝나 버립니다. 이 프로세스의 종료는 다른 프로세스들의 종료들 또한 의미 합니다.

Using a C++ Member Function as the Thread's Entry-Point-Function
C++ 맴버 함수를 스레드의 진입-점-함수로 사용하기

The example program I just listed really isn't a C++ program because it doesn't use any classes. It is just a C language program. The WIN32 API was really designed for the C language and when you employ it with C++ programs you sometimes run into difficulties. Such as this difficulty: "How can I employ a class member function (a.k.a. an instance function) as the thread's entry-point-function?"

이 예제는 클레스를 전혀 쓰지 않기 때문에 C++ 프로그램에만 국한된게 아닙니다. 그냥 C 프로그램입니다. WIN32 API 가 정말로 C 에 맞게 설계 되었기 있어서 C++ 언어를 실행하게 하면 몇가지 어려움에 봉착하게 됩니다. "어떻게 클래스 맴버(인스턴스 함수)들을 스레드의 진입-점-함수(엔트리-포인트-펑션)로 바꿀수 있을까?

If you are rusty on your C++, let me remind you of the problem. Every C++ member function has a hidden first passed parameter known as the this parameter. Via the this parameter, the function knows which instance of the class to operate upon. Because you never see these this parameters, it is easy to forget they exist.

당신이 C++ 에 대해 어느 정도 아신다면 이 문제를 기억하세요. 모든 C++ 맴버 함수는 숨겨진 첫번째 파라메터로 this 로 알려진 포인터가 있습니다. 이 파라메터를 통해서 맴버함수(인스턴스)는 어느 클레스 위에서 동작하는지 알수 있습니다. 이 파라메터는 볼수 없으니깐 그들의 존재는 잘 잊혀 집니다.

Now let's again consider the _beginthread() function which allows us to specify an arbitrary entry-point-function for our new thread. This entry-point-function must accept a single void* passed param. Aye, there's the rub. The function signature required by _beginthread() does not allow the hidden this parameter and hence a C++ member function cannot be directly activated by _beginthread().

자 다시 새 스레드를 위해 임의의 진입-점-함수를 명시하는것을 허용해주는  _beginthread() 함수를 생각해 봅시다. 이 진입-점-함수는 반드시 단일 void*  파라메터를 받아 줘야 합니다. 예 알지요, 거기에는 약간 수정해야 할게 있죠. _beginthread() 가 필요로 하는 함수 서명자에는 숨겨진 this 를 허용 하지 않으니깐 C++ 맴버 함수는 _beginthread() 함수로 바로 활성화 될수 없습니다.

We would be in a bind were it not for the facts that C and C++ are incredibly expressive languages (famously allowing you the freedom to shoot yourself in the foot) and the additional fact that _beginthread() does allow us to specify an arbitrary passed parameter to the entry-point-function.

우리는 C 와 C++ 언어가 표현력이 아주 풍부한 언어라는 사실이 아니였다면 곤경에 처했겠습니다.(유명하게도 직접 발을 차고 일어설 자유를 주죠.) 그리고 _beginthread() 함수는 임의의 진입-점-함수를 가리키는 포인터를 쓸수 있다는 사실이 존재합니다.

So we use a two-step procedure to accomplish our goal: we ask _beginthread() to employ a static class member function (which, unlike an instance function, lacks the hidden this parameter) and we send this static class function the hidden this pointer as a void*. The static class function knows to convert the void* parameter to a pointer to a class instance. Voila! We now know which instance of the class should call the real entry-point-function and this call completes the 2 step process. The relevant code (from Jaeschke's modified Part 1 Listing 1 program) is shown below:

그래서 우리는 2단계의 과정으로 우리의 목적을 완수 할 것입니다. 우선 _beginthread() 함수를 정적(스태틱) 클래스 맴버 함수로 둡니다(이건, 인스턴스 - 맴버, 함수와 다르게 숨겨진 this 파라메터가 없습니다) 그리고 이 정적 클래스 함수에게 this 포인터를 void* 로 (인수를) 보냅니다. 그 정적 클래스 함수는 void* 파라메터를 클래스 인스턴스를 가리키는 포인터로 바꿀 줄 앎니다.(자신이 그 클래스의 맴버에 존재하기 때문입니다.) 보세요(보일라~!) 우리는 어느 클래스의 인스턴스(맴버)가 진짜 진임-점-함수를 부를수 있는지 알게 되었고 이제 2단계 작업이 완료 되었습니다. 관련 코드(1부 목록 1 프로그램을 수정한 재스케의 코드)가 아래 있습니다.

 
class ThreadX

{
public:

  // In C++ you must employ a free (C) function or a static
  // class member function as the thread entry-point-function.
  // C++ 에서는 여유 있는 C 함수나 정적 클래스 맴버 함수를 쓰래드 진입-점-함수의
  // 클래스 맴버로 써야 합니다.

  static unsigned __stdcall ThreadStaticEntryPoint(void * pThis)
  {
      ThreadX * pthX = (ThreadX*)pThis;   // the tricky cast - 꽁수 변환
      pthX->ThreadEntryPoint();    // now call the true entry-point-function
                            	   // 이게 진짜 진입-점-함수

      // A thread terminates automatically if it completes execution,
      // or it can terminate itself with a call to _endthread().
      // 쓰래드는 자동으로 수행이 완료 되면 종료 됩니다. 아니면 _endthread() 자신이
      // 함수를 호출 합니다.

      return 1;          // the thread exit code - 스레드 종료 코드
  }

  void ThreadEntryPoint()
  {
     // This is the desired entry-point-function but to get
     // here we have to use a 2 step procedure involving
     // the ThreadStaticEntryPoint() function.
     // 이 진입-점-함수는 우리가 요구하는 거시만 ThreadStaticEntryPoint()를 수행하기
     // 위해 2단계의 작업이 필요 합니다.
  }
}

Then in the main() function we get the two step process started as shown below:

main() 함수는 아래 보여주는 거 처럼 2단계 작업이 필요 합니다.
 
hth1 = (HANDLE)_beginthreadex( NULL,         // security - 보안
                      0,            // stack size - 스택 크기
                      ThreadX::ThreadStaticEntryPoint,
			// entry-point-function - 진입-점-함수
                      o1,
			// arg list holding the "this" pointer -
			// this- 포인터의 인수
                      CREATE_SUSPENDED,
			// so we can later call ResumeThread()
			// 우리는 이후에 ResumeThread() 함수라 부를 겁니다.
                      &uiThread1ID );
 

Notice that I am using _beginthreadex() rather than _beginthread() to create my thread. The "ex" stands for "extended" which means this version offers additional capability not available with _beginthread(). This is typical of Microsoft's WIN32 API: when shortcomings were identified more powerful augmented techniques were introduced. One of these new extended capabilities is that the _beginthreadex() function allows me to create but not actually start my thread. I elect this choice merely so that my program better matches Jaeschke's C++/CLI code. Furthermore, _beginthreadex() allows the entry-point-function to return an unsigned value and this is handy for reporting status back to the thread creator. The thread's creator can access this status by calling GetExitCodeThread(). This is all demonstrated in the "Part 1 Listing 1" program I provide (the name comes from Jaeschke's magazine article).

제가 _beginthread() 대신에 _beginthreadex() 함수로 불러 쓰는 것을 보시기 바랍니다. "ex" 는 "extended - 확장의 줄임말입니다" 이는 이 버전은 _beginthread() 함수에서 받을수 없는 추가적인 기능을 가지고 있습니다. 이 함수는 WIN32 API 의 전형적인 함수 입니다 : 단점이 확인 되면 더 강력한 인수를 가진 기술이 소개 됩니다. 이 확장된 기능들중 하나인 _beginthreadex() 함수는 만드는데는 가능하지만 실제 내 스레드를 수행하게는 해주지 않습니다. 전 이 선택을 해야 재스케의 C++/CLI 코드 보다 더 잘 맞을거라 생각해서 골랐습니다. 더 나아가 _beginthreadex() 함수는 진입-점-함수를 부호 없는 값을 받을수 있게 하고 이는 스레드 생성자에게 성태 값을 받는걸 쉽게 해줍니다. 이 스레드 생성자는 이 상태를 GetExitThread() 함수로 접근할수 있습니다. 이 예제는 "1부 목록 1" 프로그램에 시연 되어 있습니다. (재스케의 잡지 기사에서 이름을 가져 왔습니다.)

At the end of the main() function you will see some statements which have no counterpart in Jaeschke's original program. This is because in C++/CLI the process continues until the last thread exits. That is, the threads have independent lifetimes. Hence Jaeschke's original code was designed to show that the primary thread could exit and not influence the other threads. However in C++ the process terminates when the primary thread exits and when the process terminates all its threads are then terminated. We force the primary thread (the thread that starts in the main() function) to wait upon the other two threads via the following statements:

main() 함수의 끝에 보게 될 재스케의 원 프로그램에 대응하지 않는 몇몇 부분을 보게 될 것입니다. 이 이유는 C++/CLI 프로세스는 남은 프로세스가 끝날때 까지 계속 수행하기 때문입니다. 이는 스레드는 각각의 독립적인 수행 시간을 가진다는 뜻입니다. 따라서 재스케의 원래 코드는 첫번째 스레드가 끝이 난다고 해서 다른 스레드에게 영향을 주지 않을 것이라고 설계 했습니다. 하지만 C++ 프로세스는 첫번째 스레드가 끝이 나면 다른 스레드도 덩달아 종료 됩니다. 우리는 강제로 첫번째 스레드(main() 함수에서 시작하는 스레드)를 종료 하기 위해 다음 구문들로 다른 두 스레드를 기다려야 합니다.
 
    WaitForSingleObject( hth1, INFINITE );
    WaitForSingleObject( hth2, INFINITE );
 
If you comment out these waits, the non-primary threads will never get a chance to run because the process will die when the primary thread reaches the end of the main() function.

만약 이들 기다리는 것에 대한 주석을 달지 않는다면, 첫번째 스레드가 main() 함수가 먼저 끝에 도달하면, 프로세스가 종료하게 되므로 두번째 스레드는 결코 실행할 기회를 가지지 못할 것입니다.

Synchronization Between Threads
스레드간 동기화

In the Part 1 Listing 1 program, the multiple threads don't interact with one another, and hence they cannot corrupt each other's data. The point of the Part 1 Listing 2 program is to demonstrate how this corruption comes about. This type of corruption is very difficult to debug and this makes multi-threaded programs very time consuming if you don't design them correctly. The key is to provide synchronization whenever shared data is accessed (either written or read).

1부 목록 1 프로그램에서 멀티(다중) 스레드는 다른 스레드에 영향을 주지 않는다고 했습니다. 따라서 그들은 서로 다른 자료를 변경하거나 망가뜨리지 못합니다. 1부 목록2 에서 어떻게 자료가 망가지는지 프로그램을 통해 보여 줄것입니다. 이런 변형은 디버그 하기 쉽지 않아 디자인을 제대로 하지 않은 멀티-스레드 프로그램은을 디버그하는데 시간을 오래 잡게 만들수 있습니다. 동기화의 가장 중요한 핵심은 언제든지 공유된 자료가 접근할수 있다는 것입니다.(쓰던지 읽던지)

A synchronization object is an object whose handle can be specified in one of the WIN32 wait functions such as WaitForSingleObject(). The synchronization objects provided by WIN32 are:

동기화 객체는 WIN32 에서 기다리는 함수인 WaitForSingleObject() 함수와 같이 미리 정의 된 객체 입니다. WIN32 에서 제공하는 동기화 객체는 아래와 같습니다.

  • event
  • mutex or critical section
  • semaphore
  • waitable timer

An event notifies one or more waiting threads that an event has occurred.

하나의 이벤트(사건) 하나 이상의 대기(위에 언급한 객체) 스레드에게 그 이벤트가 발생했다고 알려 줍니다.

A mutex can be owned by only one thread at a time, enabling threads to coordinate mutually exclusive access to a shared resource. The state of a mutex object is set to signaled when it is not owned by any thread, and to nonsignaled when it is owned by a thread. Only one thread at a time can own a mutex object, whose name comes from the fact that it is useful in coordinating mutually exclusive access to a shared resource.

mutex 는 한번에 한 스레드에 의해서만 소유 됩니다, 공유된 자료에 대해 서로 배타적(같이 쓸수는 있지만 쓸때는 그 스레드에서만 점유됨)으로 스레드에게 사용할수 있게 해줍니다. mutex 객체에 관한 상태는 어떤 스레드에게도 점유 되지 않았다면 신호를 보내고, 하나의 스레드에 의해 점유 되었다면 신호를 보내지 않습니다. 단 하나의 스레드만이 mutex 객체를 소유 할수 있습니다. mutex 라는 이름은 mutually exclusive 하게 자원을 접근하는게 유용하다는 사실에서 유래된 것입니다.

Critical section objects provide synchronization similar to that provided by mutex objects, except that critical section objects can be used only by the threads of a single process (hence they are lighter weight than a mutex). Like a mutex object, a critical section object can be owned by only one thread at a time, which makes it useful for protecting a shared resource from simultaneous access.

Critical section 객체들은 mutex 객체가 제공하는 거와 비슷한 동기화를 제공합니다. 대신 critical section 객체는 단일 프로세스의 스레드들에만 한정합니다.(다른 프로세스와는 공유가 되지 않습니다. 따라서 mutex 보다는 가볍습니디.) mutex 와 비슷하게 critical section 객체는 한번에 한 스레드에 의해서만 소유 될수 있습니다. 이는 동시에 접근하는 공유 자원에 대해 유용합니다.

There is no guarantee about the order in which threads will obtain ownership of the critical section, however, the operating system will be fair to all threads. Another difference between a mutex and a critical section is that if the critical section object is currently owned by another thread, EnterCriticalSection() waits indefinitely for ownership whereas WaitForSingleObject(), which is used with a mutex, allows you to specify a timeout.

스레드는 critical section 의 소유권을 가지는 것에 대한 규칙을 보증하지 않습니다 하지만, 운영체제는 모든 스레드에게 공정합니다. mutex 와 critical section 과 다른 점은 만약 다른 스레드에 의해 critical section 개체가 점유 당하면 EnterCriticalSection() 함수는 불명확(다른 스레드도 쓰기 때문)하게 소유권을 기다리는 반면 mutex 와 함께 쓰이는 WaitForSingleObject() 함수는 시간 경과를 기술하는 것을 허락해줍니다.

A semaphore maintains a count between zero and some maximum value, limiting the number of threads that are simultaneously accessing a shared resource.

semaphore 는 0 부터 최대값 까지 동시에 한 자원을 접근하는 (스레드의) 갯수를 관리합니다.

A waitable timer notifies one or more waiting threads that a specified time has arrived.

기다릴수 있는 타이머가 하나 이상의 타이머를 사용하여 스레드를 특정 시간이 올때 까지 기다리게 할수 있습니다.

This Part 1 Listing 2 program demonstrates the Critical Section synchronization object. Take a look at the source code now. Note that in the main() function we create 2 threads and ask them both to employ the same entry-point-function, namely the function called StartUp(). However because the two object instances (o1 and o2) have different values for the mover class data member, the two threads act completely different from each other.

이  1부 목록 2 프로그램은 Critical Section 동기화 객체를 시연합니다. 소스코드를 한번 보세요. main() 함수에서 우리는 2개의 스레드(o1 과 o2)를 만들고 그들에게 같은 StartUP() 이라는 진입-점-함수를 가지게 만듭니다. 하지만 각각 다른 값을 가진 이동 클레스 데이터  객체 맴버들을 가지고 있어서, 두 개의 스레드는 각각 서로 완전하게 다르게 행동합니다.

Because in one case isMover = true and in the other case isMover = false, one of the threads continually changes the Point object's x and y values while the other thread merely displays these values. But this is enough interaction that the program will display a bug if used without synchronization.

그 이유는 하나는 inMover = true 이지만 다른 하나는 inMover=false 입니다. 스레드들 중 하나는  지속적으로 포인트 객체(Point object)의 x 와 y 값을 바꾸는 동안 다른 스레드는 이들 값을 단순하게 표시만 합니다. 하지만 이는 충분하게도 동기화 없이 쓴다면 프로그램은 버그를 나타낼 반응을 가집니다.

Compile and run the program as I provide it to see the problem. Occasionally the print out of x and y values will show a discrepancy between the x and y values. When this happens the x value will be 1 larger than the y value. This happens because the thread that updates x and y was interrupted by the thread that displays the values between the moments when the x value was incremented and when the y value was incremented.

컴파일하고 제가 제공한 이 프로그램을 실행해 보세요. 가끔씩 x 와 y 값이 서로 어긋나게 보일 것입니다. x 가 y 값 보다 1 증가 할 때 발생 합니다. 이는 x 와 y 를 업데이트 시키는 스레드가 x 가 증가할때와 y 가 증가 할때 사이에 발생하는 값 표시 스레드에 의해서 중단 되기 때문입니다.

Now go to the top of the Main.cpp file and find the following statement:

이제 Main.cpp 상단 부분에 다음의 선언문을 넣습니다.
 
//#define WITH_SYNCHRONIZATION
 
Uncomment this statement (that is, remove the double slashes). Then re-compile and re-run the program. It now works perfectly. This one change activates all of the critical section statements in the program. I could have just as well used a mutex or a semaphore but the critical section is the most light-weight (hence fastest) synchronization object offered by Windows.

이 선언문에 주석 부분을 제거 합니다(더블 슬래시 부분을 제거하기). 그리고 다시 컴파일하고 프로그램을 재시작하면 잘 돌아 갑니다. 이 하나의 변화가 모든 critical section 선언문에 적용됩니다. 저는 mutex 나 semaphore 를 쓰긴 하는데 criticala section 이 윈도우즈에서 제공하는 가장 가볍게 돌아하는 동기화 객체(따라서 가장 빠릅니다)로써 자주 사용합니다.

The Producer/Consumer Paradigm
제공자/소비자 패러다임(체제)

One of the most common uses for a multi-threaded architecture is the familiar producer/consumer situation where there is one activity to create packets of stuff and another activity to receive and process those packets. The next example program comes from Jaeschke's Part 2 Listing 1 program.

한가지 사용자가 다중(멀티)-스레드 형태의 아키택쳐(구성체제)를 쓰는 가장 큰 이유중 하나는  생산자/소비자 상황에서 하나의 패킷을 보내면 바로 받을수 있는 활동 상황에 친숙하다는 것입니다. 다음 예제 프로그램은 재스케의 2부 1 목록 프로그램에서 가져 온 것입니다.

An instance of the CreateMessages class acts as the producer and an instance of the ProcessMessages class acts as the consumer. The producer creates exactly 5 messages and then commits suicide. The consumer is designed to live indefinitely, until commanded to die. The primary thread waits for the producer thread to die and then commands the consumer thread to die.

CreateMessages 클래스의 한 인스턴스(맴버함수)는 생산자 역할을 하고 ProcessMessages 클래스의 한 인스턴스 또한 소비자 역할을 합니다. 이 생산자는 정확히 5개의 메시지를 생성하고 그리고 소멸합니다. 소비자는 확실하지 않게(언제 생성하고 활동할지 불명확함) 존재하게 설계되어 있어서 소멸하기 전까지 계속 명령을 내립니다. 첫번째(1순위) 스레드가 생산자 스레드의 파괴를 기다리고 소비자 스레드에게 파괴를 명령하고 기다립니다.

The program has a single instance of the MessageBuffer class and this one instance is shared by both the producer and consumer threads. Via synchronization statements, this program guarantees that the consumer thread can't process the contents of the message buffer until the producer thread has put something there, and that the producer thread can't put another message there until the previous one has been consumed.

이 프로그램은 MessageBuffer 라는 클래스의 단일 인스턴스를 가지고 있고, 이 스레드는 생산자와 소비자 스레드를 포함하고 있습니다. 동기화 지시문으로, 이 프로그램은 소비자 스레드가 생산자 스레드가 무언가 넣기 전까지는 컨텐츠 가공을 수행할수 없습니다. 그리고 생산자 스레드는 이전 메시지가 쓰이기 전까지 새로운 메시지를 만들수 없습니다.

Since my Part 1 Listing 2 program demonstrates the critical section, I elected to employ a mutex in this Part 2 Listing 1 program. As with the Part 1 Listing 2 example program, if you simply compile and run the Part 2 Listing 1 program as I provide it, you will see that it has a bug. Whereas the producer creates the 5 following messages:

저의 1부 목록 2 프로그램에서 critical section 보여 주었으니 이번에는 mutex 를 사용해서 2부 목록 1 프로그램을 보여 드리겠습니다. 1부 목록 2 예제 프로그램에 보시는 봐와 같이 만약 2 부 목록 1 프로그램을 그냥 컴파일 해서 실행하시면, 버그를 보시게 될겁니다. 생산자 (스레드는) 5개의 아래 메시지를 생성합니다.

1111111111
2222222222
3333333333
4444444444
5555555555
the consumer receives the 5 following messages:

소비자 (스레드)는 5개의 메시지를 받게 됩니다.

1
2111111111
3222222222
4333333333
5444444444

There is clearly a synchronization problem: the consumer is getting access to the message buffer as soon as the producer has updated the first character of the new message. But the rest of the message buffer has not yet been updated.

이는 명백히 동기화 문제 입니다. 소비자는 바로 생산자가 첫번째 메시지의 첫문자를 생산하자 마자 접근을 해야 합니다. 하지만 남은 메시지의 버퍼는 아직 갱신 되지 않았습니다.

Now go to the top of the Main.cpp file and find the following statement:

다시 Main.cpp 로 돌아가서 다음 선언문을 찾아 가세요.

 
//#define WITH_SYNCHRONIZATION
 

Uncomment this statement (that is, remove the double slashes). Then re-compile and re-run the program. It now works perfectly.

위 선언문에서 주석 부분을 제거하세요(다시 말해, 더블 슬래시를 제거) 그리고 다시 컴파일 하시고 재실행 하면 잘 돌아 갑니다.

Between the English explanation in Jaeschke's original magazine article and all the comments I have put in my C++ source code, you should be able to follow the flow. The final comment I will make is that the GetExitCodeThread() function returns the special value 259 when the thread is still alive (and hence hasn't really exited). You can find the definition for this value in the WinBase header file:

재시케의 원 잡기 글과 제 C++ 소스 코드에 놓인 영문(한글) 주석문을 통해 여러분은 흐름을 이해 하실수 있습니다. 마지막 주석문으로 GetExitCodeThread() 함수에 관해 쓰겠는데요 이 함수는 스레드가 아직 살아 남으면(그리고 따라서 정말 종료하지 않았다면) 특별한 259 값을 반환 합니다. 사용자는 WinBase 헤더에서 이 값에 대한 정의를 발견 하실수 있습니다.
 
#define STILL_ACTIVE   STATUS_PENDING
 

where you find STATUS_PENDING defined in the WinNT.h header file:

STATUS_PENDING (상태 유보) 정의를 WinNT.h 헤더 파일에서 정의 된 것을 보실수 있습니다.
 
#define STATUS_PENDING    ((DWORD   )0x00000103L)
 
Note that 0x00000103 = 259.

16진수로 103 은 10진수로 259 입니다.

Thread Local Storage
스레드 지역 저장소(로컬 스로리지)

Jaeschke's Part 2 Listing 3 program demonstrates thread local storage. Thread local storage is memory that is accessible only to a single thread. At the start of this article I said that an operating system could initiate a new thread faster than it could initiate a new process because all threads share the same memory space (including the heap) and hence there is less that the operating system needs to set up when creating a new thread. But here is the exception to that rule. When you request thread local storage you are asking the operating system to erect a wall around certain memory locations in order that only a single one of the threads may access that memory.

재시케의 2부 목록 3 프로그램은 스레드 지역 저장소를 보여 줍니다. 스레드 지역 저장소는 단지 단일 스레드 만이 접근할 수 있는 저장공간입니다. 이 글의 시작 부분에서 운영체제에서는 새로운 스레드를 생성하는 것이 새 프로세스를 생성하는 거 보다 빠를수 있다고 했는데 그 이유는 모든 스레드는 같은 메모리공간(힙 공간을 포함)을 공유 하기 떄문이라고 말했습니다. 하지만 예외 규칙이 존재 합니다. 스레드 지역 저장소를 요청할려면 당신은 운영제제에게 단일 스레드만이 접근할수 있는 메모리 공간을 벽으로 보호해 달라고 요청하면 됩니다.

The C++ keyword which declares that a variable should employ thread local storage is __declspec(thread).

C++ 키워드로 스레드 지역 저장소를 선언하기를  __declspace(thread) 로 하시면 됩니다.

As with my other example programs, this one will display an obvious synchronization problem if you compile and run it unchanged. After you have seen the problem go to the top of the Main.cpp file and find the following statement:
제 다른 예제 프로그램에서 마찬가지로, 이 프로그램도 변경하지 않고 그냥 컴파일 해서 실행하면 명백한 동기화 문제가 생깁니다. 문제를 발견하면 다시 Main.cpp 코드로 가서 다시 수정하시기 바랍니다.
 
//#define WITH_SYNCHRONIZATION
 

Uncomment this statement (that is, remove the double slashes). Then
re-compile and re-run the program. It now works perfectly.


주석 부분을 제거하시고 (슬레쉬 두개를 지우세요) 다시 컴파일 하고 실행하시면 완벽하게
동작합니다.

Atomicity
원자성

Jaeschke's Part 2 Listing 4 program demonstrates the problem of atomicity which is the situation where an operation will fail if it is interrupted mid-way through. This usage of the word "atomic" relates back to the time when an atom was believed to be the smallest particle of matter and hence something that couldn't be further split. Assembly language statements are naturally atomic: they cannot be interrupted half-way through.

재스케의 2부 목록 4의 프로그램은 중간 단계에서 실행 중에 중단 되면 작업이 실패 되는 상황을 가진 원자성의 문제를 지닌 프로그램을 보여줍니다. 이를 "원자"와 연관된 단어로 사용되는데 이는 옛날 원자가 가장 작은 존재여서 더이상 쪼개어 지지 않는다고 믿어 와서 붙여진 단어입니다. 어셈블리 언어는 자연적으로 원자성을 가집니다 : 더 이상 반으로 언어를 쪼개지지 않습니다.(유사 명령어는 제외)

This is not true of high-level C or C++ statements. Whereas you might consider an update to a 64 bit variable to be an atomic operation, it actually isn't on 32 bit hardware. Microsoft's WIN32 API offers the InterlockedIncrement() function as the solution for this type of atomicity problem.

이 사실은 상위 C / C++ 언어에서는 적용되지 않습니다. 반면 64 비트 변수를 갱신한다고 생각하면 사실 32 비트 하드웨어에서 실제로 존재 하지 않으므로 원자성의 작업을 고려 해야 합니다. 마이크로소프트의 WIN32 API 는 interlockedincrement() 함수로 원자성에 관련된 문제에 대한 해법을 제시 합니다.

This example program could be rewritten to employ 64 bit integers (the LONGLONG data type) and the InterlockedIncrement64() function if it only needed to run under Windows 2003 Server. But, alas, Windows XP does not support InterlockedIncrement64(). Hence I was originally worried that I wouldn't be able to demonstrate an atomicity bug in a Windows XP program that dealt only with 32 bit integers.

이 예제 프로그램은 64 비트 정수(LONGLONG 형식)으로 다시 쓰여질수 있고 윈도우즈 2003 서버에서만 실행한다면 interlockedincrement64() 함수로 표현이 가능합니다. 하지만 슬프게도 Windows XP 는 interlockedincrement64() 함수를 지원하지 않습니다. 따라서 저는 처음부터 32 비트 정수만 다루는 윈도우즈 XP 에서 원자성에 관련된 문제를 표현하지 못할거 같아 걱정 했었습니다.

But, curiously, such a bug can be demonstrated as long as we employ the Debug mode settings in the Visual C++ .NET 2003 compiler rather than the Release mode settings. Therefore, you will notice that unlike the other example programs inside the .ZIP file that I distribute, this one is set for a Debug configuration.

그런데 신기하게도 비주얼 스튜디오 C++ .NET 2003 에서 발매(릴리즈) 모드 대신 디버그 모드로 수행하면 이 원자성에 관련된 문제를 표현할 수 있는 버그가 존재 합니다.

As with my other example programs, this one will display an obvious synchronization problem if you compile and run it unchanged. After you have seen the problem go to the top of the Main.cpp file and find the following statement:

다른 예제 프로그램들 처럼 이 프로그램도 코드를 수정하지 않으면  동기화 문제가 존재 합니다. Main.cpp 에 다음의 문장을 추가 합니다.
static boolinterlocked = false // change this to fix the problem
                               // 이 부분 바꾸어 문제를 해결합니다.
Change false to true and then re-compile and re-run the program. It now works perfectly because it is now employing InterlockedIncrement().
false 을 true 로 바꾸어 다시 컴파일 하고 실행하면 정상 동작합니다. 이유는 이제 interlockedincrement() 함수를 쓰기 떄문입니다.

The Example Programs
예제 프로그램

In order that other C++ programmers can experiment with these multithreaded examples I make available a .ZIP file holding five Visual C++ .NET 2003 workspaces for the Part 1 Listing 1, Part 1 Listing 2, Part 2 Listing 1, Part 2 Listing 3, and Part 2 Listing 4 programs from Jaeschke's original article (now translated to C++). Enjoy !

다른 C++ 프로그래머들이 다중(멀티) 스레드 예제를 실험 해보기 위해 1부 목록1, 2, 그리고 2부 목록 3, 4 으로 구성된 5 개의 비주얼 스튜디오 C++ .NET 2003 워크스패이스 (작업공간) 파일을 .ZIP 파일로 압축해서 제공하고 있습니다. 받아서 해보세요!

About the Author
저자에 관해

This is my second submission to CodeProject. The first demonstrated how to use Direct3D 8 to model the Munsell color solid so that you could then fly through this color cube as in a video game. I also have a web site where I offer a complete introduction to programming, including assembly language programming. My home page is:

이 문서는 코드프로젝트에 보내는 2번쨰 글입니다. 처음 보여준것은 Direct 3D 8을 이용해서 어떻게  Munsell 불투명 색을 모델해서 비디오 게임 내에 이 색을 구동(날리게)할수 있을지 보여준 내용이였습니다. 저는 또한 어셈블리 언어를 포함안 전체적인 프로그래밍의 소개를 담고 있는 웹사이트를 제공하고 있습니다. 저의 홈페이지는

http://www.computersciencelab.com/

John Kopplin

존 코핀