C# 멀티스레딩

냐옹·2024년 4월 9일
0

.NET

목록 보기
16/31

멀티스레딩

시작은 다음과 같다. 총선 프로그램을 보면 처음에 사용자가 타이머 주기를 선택하고 그 주기대로 DB를 업데이트하거나, XML을 최신화하거나 하는데, 그때마다 해당 타이머의 Tick() 메서드가 실행된다.

예를 들어서 Tick메서드에서 주기적으로 ExFunc()을 실행하기를 원한다고 쳐보자.
원래 코드에서는
private void Timer_tick(){
스레드이름 = new Thread(new ThreadStart(ExFunc));
스레드이름.Start();
}
과 같이 실행하게 된다.

이경우에 디버깅을 해보면 이런 것들을 볼 수 있다.
스레드를 매 주기마다 계속 생성하고 없애고 있다. 스레드를 만드는 이유는 WINFORM프로그램에서는 기본적인 동작이 UI스레드에서 돌게 되는데, 여기에서 가벼운 작업 정도는 괜찮다. 그런데 무거운 작업이나 ( 아마도 주기적인 작업도 마찬가지겠지..? ) 그런걸 돌리게 되면 UI스레드가 멈출 수 있다. ( 인터렉션이..., 사용자 입력이나 클릭 같은 것들이 안먹힐 수 있다. )
이런 경우에 다음과 같은 단점들이 있다.
1. 스레드의 생명주기를 개발자가 모두 관리해야 한다.
2. 매번 새로운 스레드를 생성하는 것은 많은 리소스 사용을 동반하고 이는 성능에 영향을 줄 수 있다.
3. 스레드 생성과 종료는 비용이 많이 드는 작업이므로, 빈번한 스레드 생성과 종료는 성능 저하를 초해할 수 있다.
그래서 대안으로 Task.Run() 메서드로 돌리는 것이 권장되는데, 이것은 스레드 풀을 사용해서 작업을 비동기적으로 실행하는 것이다. 스레드 풀은 미리 생성된 스레드의 집합을 유지 관리하고, 작업이 발생하면 사용가능한 스레드 중 하나에 작업을 할당한다. 작업이 끝나고 사용된 스레드는 스레드 풀로 반환되어서 다른 작업에 재사용될 수 있다. 이는 다음과 같은 장점이 있다.
1. 스레드 풀을 사용하기 때문에 스레드 생성과 종료에 따른 오버헤드가 감소한다. 스레드가 재사용되기 때문에 자원 사용이 최적화된다.
2. 스레드 풀은 작업을 효율적으로 관리하여 성능을 개선할 수 있다.
3. Task 기반 비동기 프로그래밍 모델을 사용하면 복잡한 스레드 관리 없이 비동기 작업을 쉽게 구현할 수 있다.
Task.Run은 별도의 스레드를 하나만 돌리는게 아니라 필요에 따라서 스레드 풀 내의 여러 스레드를 활용할 수 있다. 특정 시점에 Task.Run으로 실행되는 코드가 여러개 있으면 스레드 풀 내의 여러 스레드에서 동시에 다른 작업을 수행할 수도 있다. 효율적이고 간편하다.

Task.Run은 닷넷 비동기 역할에서 중요하다. 이를 사용하면 비동기 작업을 간단하게 실행할 수 있다. Task.Run을 사용하면 코드의 가독성과 유지보수성을 높이면서 비동기적으로 작업을 처리할 수 있다.

Task.Run() 메서드는 Action 델리게이트를 매개변수로 받아서 해당 작업을 비동기적으로 실행한다. 반환되는 Task 객체를 통해 비동기 작업의 완료를 기다리거나, 결과를 받아올 수 있다. ( 작업이 결과를 반환하는 경우에 )

Task.Run의 비동기 작업
Task.Run으로 실행된 작업은 비동기적으로 처리된다. 이것은 호출스레드 ( 예를 들어서 UI스레드 )가 작업의 완료를 기다리지 않고, 즉시 다음 코드 줄로 진행한다는 의미다. 작업의 결과나 완료를 기다리려면 await 키워드를 사용하여 Task 객체를 기다릴 수 있다.
await Task.Run(() => {});
하지만 다음을 조심해야한다. 한번 겪어봤는데, 크로스 스레딩을 주의해야 한다. UI 요소에는 UI스레드에서만 접근해야한다. 아니면 에러가 뜬다. 때문에 대리자를 사용해서 UI스레드로 접근하는 것을 마샬링이라고 한다. 보통 Control(UI요소).Invoke를 사용한다.

UI 마샬링 ( UI Marshaling )
이는 백그라운드 스레드에서 실행 중인 작업이 UI 컴포넌트를 안전하게 업데이트할 수 있도록 UI스레드로 작업을 전환하는 과정이다. 대부분의 UI 프레임워크들은 UI 컴포넌트가 생성도니 스레드에서만 (일반적으로 UI 메인스레드) 이들을 업데이트하도록 제한하고, 이는 UI요소에 대한 동시성 문제를 방지하기 위함이다. 따라서 백그라운드 스레드에서 UI컴포넌트에 접근하려면 UI 스레드로 작업을 전환해야 한다.

닷넷에서의 UI 마샬링 방법 (Window Forms)
window Forms에서는 Control.Invoke()와 Control.BeginInvoke()메서드를 사용하여 UI 스레드로 마샬링을 수행한다. Invoke는 동기적으로 작업을 수행하고 BeginInvoke는 비동기적으로 작업을 수행한다.

비동기 메서드 사용
비동기 프로그래밍을 사요할 때는 async와 await 키워드를 사용하여 UI 스레드로의 마샬링을 더 쉽게 할 수 있다. 비동기 메서드 내에서 await 키워드를 사용하면 메서드의 나머지 부분은 UI 스레드에서 실행된다. ( 메서드가 UI 스레드에서 시작된 경우 )
public async Task UpdateUIAsync()
{
// 백그라운드 작업
await Task.Run(() =>
{
// 시간이 걸리는 작업
});

// UI 스레드에서 실행됨
label.Text = "Updated in UI thread";

}

Task Parallel Library _ TPL
닷넷 4.0부터 도입된 병렬 프로그래밍을 위한 고수준의 API 집합니다. TPL을 사용하면 CPU의 여러 코어를 효율적으로 활용하여 작업을 병렬로 실행할 수 있으며, 비동기 프로그래밍을 간소화할 수 있다. TPL은 병렬 작업을 쉽게 구현할 수 있도록 설계되었으며, 개발자가 직접 스레드를 관리하는 대신 작업 기반의 비동기 패턴을 사용한다.

TPL의 핵심은 Task 클래스이다. Task는 비동기 작업을 나타낸다.
using System;
using System.Threading.Tasks;

class Program
{
static void Main(string[] args)
{
// Task를 사용하여 비동기 작업 실행
Task task = Task.Run(() =>
{
// 여기에 비동기로 수행할 작업을 넣습니다.
Console.WriteLine("Hello from task!");
});

    // Task의 완료를 기다림
    task.Wait();
}

}

병렬 PLINQ _ PLINQ는 LINQ 패턴의 병렬구현이다.
모든 IEnumerable 구현 개체에 대해서 작동한다.
병렬 실행을 통해서 PLINQ는 종종 데이터 소스에 AsParallel 쿼리 작업을 추가하여 특정한 쿼리 종류의 레거시 코드에 대한 큰 성능 개선을 얻을 수 있다. 병렬로 실행한다고 해서 모든 쿼리에 대해서 속도가 빨라진다는 것을 의미하지는 않는다. 병렬화는 사실 실제로 특정 쿼리의 속도를 낮춘다.
PLINQ는 보수적으로 작동한다.
런타임 시에 PLINQ 인프라는 쿼리의 전체 구조를 분석한다. 쿼리가 병렬화로 속도가 향상될 가능성이 있는 경우에 PLINQ는 동시에 실행될 수 있는 작업으로 소스 시퀀스를 분할한다. 쿼리를 병렬화하는 것이 안전하지 않는 경우 PLINQ는 쿼리를 순차적으로 실행한다. PLINQ가 잠재적으로 비용이 많이 드는 병렬 알고리즘 또는 저렴한 순차적 알고리즘 중 하나를 선택할 수 있는 경우 기본적으로 순차적 알고리즘을 선택한다.
 병렬 스레드 처리랑 EF core에서의 DbContext 사용은 주의해야한다.

ParallelEnumerable 클래스
이 클래스에는 병렬 실행과 관련된 동작을 사용하도록 하는 메서드들이 여럿 포함

  • AsParallel()
     PLINQ에 대한 진입점이다. 가능한 경우 나머지 쿼리가 병렬화 되어야 한다는 것을 지정한다.
  • AsSequential()
     비정렬 LINQ 쿼리로 나머지 쿼리가 순차적으로 실행되어야 한다는 것을 지정한다.
  • AsOrdered()
     plinq가 나머지 쿼리에 대해 또는 순서 지정이 변경될 때까지 소스 시퀀스의 순서지정 ( 예시로 Order by)을 유지해야한다는 것을 지정한다.
  • AsUnordered()
     쿼리의 나머지 부분에 대해서 소스 시퀀스의 순서 지정을 유지하기 위해 plinq가 필요 없다.

0개의 댓글