본문 바로가기
Delphi/클래스

델파이 쓰레드 사용법

by MonoSoft 2021. 5. 21.
728x90
반응형

델파이 쓰레드 사용법

 

1. 쓰레드란 무엇인가?

 

A 라는 프로그램과 B 라는 프로그램이 실행중에 있다.

이러한 경우 A 프로그램에서 B 프로그램의 전역변수에 접근할 수 있는가?

물론 불가능하다.

이것이 가능하다는 것은 여러분이 즐기고 있는 벽돌깨기 프로그램이

MP3 플레이어 프로그램의 메모리 영역에 접근할 수 있다는 소리와 같은 것이다.

프로세스마다 독립적으로 메모리를 유지하기 때문에 이러한 일은 불가능하다.

 

부모 프로세스가 자식 프로세스를 생성하는 경우도 이와 같다.

생성되는 모든 자식 프로세스는 자신만의 독립적인 메모리 공간을 소유하게 된다.

 

그러나 쓰레드를 생성하게 되는 경우,

스택(Stack) 메모리 공간만 독립적으로 유지하게 되며

일반적인 나머지 메모리 영역은 공유된다.

일단은 프로그램상에서 쓰레드들은 전역 변수로의

접근이 모두 허용된다고 생각하면 충분할 것이다.

즉, 전역변수들은 생성되는 모든 쓰레드들에 의해서 공유된다.

 

프로세스를 생성하는 것은 운영체제 입장에서 보면 메모리면에서나

연산에 필요한 시간 할당에 있어서나 상당히 부담이 되는 작업이다.

생성과정에서뿐만 아니라 생성 이후에도 큰 부담이 된다.

그럼에도 불구하고 우리는 다중 접속 서버의 구현을 위해

클라이언트 연결 요청 하나당 프로세스를 생성했었다.

그만큼 부담이 되는 작업이라 할 수 있다.

 

무엇 때문에 생성 이후에도 큰 부담을 안겨주는가?

여러가지 이유가 있지만 그중에서도 가장 중요한 내용 한가지만 언급해 보겠다.

 

여러 개의 프로세스가 동시에 실행될 수 있는 이유는

프로세스들이 CPU 의 할당시간을 나누기 때문이다.

일반적으로 프로세스의 수가 CPU 의 수보다 많지 않은가?

따라서 CPU 할당 시간을 얻게 되는 프로세스가 메인 메모리로 올라오고,

이전에 실행되던 프로세스는 메모리에서 내려와야 한다.

이런 식으로 CPU 할당 시간을 얻게 되는 프로세스가 변경되는 시점에서 거쳐야 하는

몇몇 과정을 일컬어 ‘컨텍스트 스위칭(context switching)’이라 한다.

일부 운영체제 관련 한글책에서는 ‘문맥 전환’이라고도 한다.

이 작업은 여러 개의 프로세스가 동시에 실행될 때 상당히 많은 부분 시스템에게 부담을 준다.

왜냐하면 프로세스가 많다는 것은 그만큼 CPU 할당시간을 더 짧게 나누어서

프로세스들에게 할당해야 한다는 소리가 되고,

이것은 결국 잦은 ‘컨택스트 스위칭’으로 이어지게 되기 때문이다.

 

쓰레드도 ‘컨택스트 스위칭’과정이 없는 것은 아니다.

따라서 쓰레드 역시 많이 생성되면 그만큼 시스템에 부담을 안겨 줄 것이다.

그러나 프로세스의 ‘컨텍스트 스위칭’에 비교해 보면 그 과정이 상대적으로 간단하게 처리된다. 메모리만 보더라도 스택 공간만 독립적으로 유지하다보니,

일부 메모리만 메인 메모리에서 내리고 올려주면 된다.

프로세스와 쓰레드의 생성시간만 가지고 비교를 해 보더라도

쓰레드가 생성되는 시간이 프로세스 생성 시간에 비해서 대략 스무배에서

최대 백배까지 빠르다고 한다.

 

 

안정성 면에서는 쓰레드보다는 프로세스간 통신을 이용하는 것이 낫다.

쓰레드를 효율적으로 사용한다면 굳이 프로세스간 통신을 이용할 필요야 없겠지만

일반적인 개발자들의 코드상 때에 따라서는 쓰레드가 아닌

프로세스를 생성해서 프로세스간 통신을 이용하는 것이 좋을 수도 있다.

 

 

2. 쓰레드 사용상의 유의사항

1) 공유 자원

쓰레드 사용시 가장 문제가 되는 것은 공유 자원에 대한 접근문제이다.

2개 이상의 쓰레드가 공유자원에 접근하는 경우에 발생하는 문제로 예를 들면 아래와 같다.

 

 

위 조건절에서 Count 가 1 인 경우, Main Thread 는 삭제를 하려고 한다.

하지만 다른 Thread 가 Main Thread 보다 먼저 삭제를 했다면

Main Thread 는 count 가 0인 리스트를 삭제하려고 하기 때문에 에러가 발생하게 된다.

 

주의해야 하는 자원은 다음의

그림과 같이 2개 이상의 쓰레드가 접근이 가능한 전역 자료형이다.

 

2) 우선순위

쓰레드는 생성시 자신의 우선순위를 설정할 수 있다.

우선순위란 CPU 자원을 얼마나 더 사용할 것인지를 결정하는 것이다.

우선 순위를 높게 설정할수록 더 많은 CPU 사용시간을 가짐으로써

코드의 성능을 최대한 이끌어낼 수 있게된다.

하지만 다른 어플리케이션의 성능에 영향을 미친다는 것을 기억해야 한다.

 

델파이는 아래와 같은 설정값을 제공한다.

tpIdle, tpLowest, tpLower, tpNormal, tpHigher, tpHighest, tpTimeCritical

 

쓰레드_1.zip
0.44MB

 

델파이에서 쓰레드 사용은 조심해야 합니다.

 

특히 VCL 의 속성을 변경하려고 하는것은 프로그램의 예기치 못한 종료를 불러올 수 있습니다.

델파이의 VCL의 처리(특히 눈에 보이는 화면 전환)는 프로그램의 메인 쓰레드에서

처리하게 되는데 프로그래머가 다른 쓰레드를 이용해 VCL의 속성을 변경해 버리면

메인 쓰레드에서 오류가 발생할 확률이 엄청 높아집니다.

(물론 반드시 오류가 발생한다는 것은 아닙니다.)

 

그러므로 GUI 를 위해 쓰레드를 쓰는것은 권장하지 않습니다.

 

그런데... 사실 델파이를 사용하다보면 쓰레드를 사용하지 않고서는 사용자 화면이 모든 작업이

종료되기 전까지는 멈춰있습니다. 즉, 쓰레드를 사용하지 않을 수 없습니다.

 

그래서 여기에서는 안전하게 쓰레드에서 VCL의 속성을 변경하는 방법을 제시하고자 합니다.

 

이때 사용할수 있는 함수가 Syncronize() 입니다.

 

일단 TSomeThread 라는 Unit 이 있다고 하고 이는 TThread를 상속 받았다고 한다면,

Execute 함수가 당연히 존재합니다. 그리고 Syncronize() 는 다음과 같이 쓰입니다.

 

 

procedure TSomeThread.Execute;

begin

  Synchronize(UpdateVCL);

end;

 

procedure TSomeThread.UpdateVCL;

begin

  FormMain.Edit1.Text('텍스트 갱신');

end;

 

여기서 UpdateVCL() 은 제가 임의로 정의한 프로시저 입니다. 즉, Syncronize() 를 통해

VCL의 요소를 갱신하는 프로시저를 실행시키면 안전함이 보장 됩니다.

 

그런데 여기서 Syncronize() 는 오직 프로시저만 가능하며,

실행하는 프로시저는 넘기는 인자값(파라미터)이 없는

프로시저만 가능합니다.

 

즉, 어떠한것도 넘겨줄수도, 결과 값을 받을 수도 없습니다.

 

그렇다면 FromMain.Edit1 의 Text를 임의의 값으로 변경하려면 어떡해야 할까요?

그리고 제대로 변경되었다면 해당 결과는 어떻게 쓰레드 안에서 확인해야 할까요?

 

방법은 한가지 뿐입니다. 임의의 Text 값을 전역 변수에 저장한다음

Syncronize() 를 통해 화면 갱신하게 하고, 다시 결과를 다른 변수로 확인해야 합니다.

 

말로는 설명이 어렵고 예시 코드는 다음과 같습니다.

 

//전역 변수

var

someText : String; //임의의 텍스트 저장

resultProcedure: Boolean; //결과의 저장

 

procedure TSomeThread.Execute;

begin

  //변수에 값을 저장하고

  someText := 'SomeText';

 

  //VCL을 갱신

  Synchronize(UpdateVCL);

 

  //결과의 확인

  if resultProcedure = true then

  begin

    ShowMessage('변경에 성공 하였습니다.');

  end;

end;

 

procedure TSomeThread.UpdateVCL;

begin

  //VCL 변경

  FormMain.Edit1.Text(someText);

  //변경 결과의 저장

  if FormMain.Edit1.Text = 'SomeText' then

  begin

    resultProcedure := true;

  end;

end;

 

Syncronize() 가 실행하는 동안에는

어플리케이션의 프로그램 메인 스레드가 동작하지 못합니다.

 

즉, 메인 쓰레드를 멈춰 놓고 해당 작업을 실행하기 때문에 반드시 안전함을 보장할 수 있습니다.

 

당연히, Syncronize() 의 남발은, 쓰레드를 사용하는 의미를 퇴색시키며, 멀티 프로그래밍과 거리가 멀어집니다.

 

최대한, 프로그래머가 사용하는 쓰레드는 VCL과는 별개의 다른 작업을 처리하는 작업을 하도록 하여

Syncronize() 의 사용을 최대한으로 줄여야 할 것 입니다.

 

unit Unit1;

 

interface

 

uses

Winapi.Windows, Winapi.Messages, System.SysUtils,  System.Variants, System.Classes, Vcl.Graphics,

Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

 

type

TForm1 = class(TForm)

Memo1: TMemo;

Button1: TButton;

Button2: TButton;

procedure Button1Click(Sender: TObject);

procedure FormMouseDown(Sender: TObject; Button: TMouseButton;

Shift: TShiftState; X, Y: Integer);

procedure Button2Click(Sender: TObject);

private

{ Private declarations }

public

{ Public declarations }

 

end;

 

TMyThread = class(TThread)

private

public

procedure aaa;

procedure Execute; override;

end;

 

var

Form1: TForm1;

a : STring;

 

implementation

 

uses unit2;

 

{$R *.dfm}

 

{ TMyThread }

 

procedure TMyThread.Execute();

var

Work : TWorkClass;

i : integer;

begin

Work := TWorkClass.Create('Work Class가 생성되었습니다.');

 

for I := 1 to 10000 do

begin

if a = '1' then

Synchronize(aaa);

 

Work.Next();

Form1.Memo1.Lines.Add(intTostr(Work.GetCount()));

end;

 

Work.Free;

end;

 

procedure TMyThread.aaa;

begin

Form1.Memo1.Lines.Add('a');

ShowMessage('a');

end;

 

procedure TForm1.Button1Click(Sender: TObject);

var

Work : TWorkClass;

i : integer;

begin

Work := TWorkClass.Create('Work Class가 생성되었습니다.');

 

for I := 1 to 10000 do

begin

Work.Next();

Memo1.Lines.Add(intTostr(Work.GetCount()));

end;

 

Work.Free;

 

end;

 

procedure TForm1.Button2Click(Sender: TObject);

var

MyThread : TThread;

begin

MyThread := TMyThread.Create(True);

MyThread.Resume;

 

end;

 

procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;

Shift: TShiftState; X, Y: Integer);

begin

a := '1';

Memo1.Lines.Add('OnMouseDown Enevt');

end;

end.

728x90
반응형

댓글