C #에서 스레드 풀을 언제 사용합니까? [닫은]


127

C #에서 멀티 스레드 프로그래밍을 배우려고 노력 중이며 스레드 풀을 사용하는 것이 가장 좋으며 내 스레드를 만드는 것이 가장 혼란 스럽습니다. 한 책은 작은 작업에만 스레드 풀을 사용하는 것이 좋지만 (그것이 무엇이든간에) 실제 지침을 찾을 수없는 것 같습니다. 이 프로그래밍 결정을 할 때 고려해야 할 사항은 무엇입니까?

답변:


47

지속적인 처리가 필요한 논리적 인 작업이 많고 동시에 수행하려면 pool + scheduler를 사용하십시오.

원격 서버 또는 디스크 액세스에서 자료 다운로드와 같은 IO 관련 작업을 동시에 수행해야하지만 몇 분마다 한 번씩 말해야하는 경우, 고유 한 스레드를 작성하고 완료되면 종료하십시오.

편집 : 몇 가지 고려 사항에 대해 데이터베이스 액세스, 물리 / 시뮬레이션, AI (게임) 및 많은 사용자 정의 작업을 처리하는 가상 시스템에서 실행되는 스크립트 작업에 스레드 풀을 사용합니다.

일반적으로 풀은 프로세서 당 2 개의 스레드 (현재 요즘 4 개)로 구성되지만 필요한 수를 알고있는 경우 원하는 스레드 수를 설정할 수 있습니다.

편집 : 자신의 스레드를 만드는 이유는 컨텍스트 변경 때문입니다 (스레드가 메모리와 함께 프로세스 안팎으로 스왑 해야하는 경우). 쓰레드를 사용하지 않을 때와 같이 쓸모없는 컨텍스트 변경이 발생하면 예를 들어 마치 그대로두면 프로그램 성능이 쉽게 절반으로 떨어질 수 있습니다 (예 : 3 개의 휴면 스레드와 2 개의 활성 스레드가 있음). 따라서 다운로드 스레드가 대기 중이면 많은 CPU를 소비하고 실제 응용 프로그램의 캐시를 냉각시킵니다.


2
좋아, 그러나 이것이 어떻게 접근하는지 설명 할 수 있습니까? 예를 들어, 스레드 풀을 사용하여 원격 서버에서 다운로드하거나 디스크 IO를 수행 할 때의 단점은 무엇입니까?

8
스레드가 동기화 오브젝트 (이벤트, 세마포어, 뮤텍스 등)를 기다리고있는 경우 스레드는 CPU를 소비하지 않습니다.
Brannon

7
Brannon이 말했듯이, 공통적 인 신화는 여러 스레드 생성이 성능에 영향을 미친다는 것입니다. 실제로 사용되지 않은 스레드는 리소스를 거의 사용하지 않습니다. 컨텍스트 스위치는 매우 수요가 많은 서버에서만 문제가되기 시작합니다 (이 경우 대안은 I / O 완료 포트 참조).
FDCastel

12
유휴 스레드가 성능에 영향을 줍니까? 기다리는 방법에 따라 다릅니다. 동기화 개체를 올바르게 작성하고 기다리는 경우 CPU 리소스를 소비하지 않아야합니다. 결과를 확인하기 위해 주기적으로 깨어나는 루프를 기다리는 경우 CPU를 낭비하는 것입니다. 항상 좋은 코딩으로 귀결됩니다.
Bill

2
유휴 관리 스레드는 스택에 메모리를 사용합니다. 기본적으로 스레드 당 1MiB입니다. 따라서 모든 스레드가 작동하는 것이 좋습니다.
Vadym Stetsiak가

48

다른 언어와 같은 이유로 C #에서 스레드 풀을 사용하는 것이 좋습니다.

실행중인 스레드 수를 제한하거나 스레드를 작성 및 삭제하는 오버 헤드를 원하지 않는 경우 스레드 풀을 사용하십시오.

작은 작업으로 읽는 책은 수명이 짧은 작업을 의미합니다. 1 초 동안 만 실행되는 스레드를 만드는 데 10 초가 걸리는 경우 풀을 사용해야하는 곳입니다 (실제 수치는 무시하고 비율입니다).

그렇지 않으면 단순히 의도 한 작업을 수행하지 않고 스레드를 작성하고 파괴하는 데 많은 시간을 소비합니다.


28

.Net의 스레드 풀에 대한 요약은 다음과 같습니다. http://blogs.msdn.com/pedram/archive/2007/08/05/dedicated-thread-or-a-threadpool-thread.aspx

게시물에는 스레드 풀을 사용하지 말고 자체 스레드를 대신 시작해야 할 때도 있습니다.


8
링크의 경우 -1 나는 그것이 좋은 연결이라고 확신하지만 SO가 충분할 것으로 기대합니다.
Jon Davis

26
@ stimpy77-그것은 잘못된 기대입니다. SO는 모든 질문에 대한 궁극적 인 권한이 아니며 각 주제에 대한 모든 심층 정보가 해당 주제에 해당하는 모든 SO 답변에 복제 될 수 있고 중복되어야하기 때문에 결코 자급 자족 할 수 없습니다. (그리고 나는 당신이 아웃 바운드 링크를 가진 Jon Skeet의 모든 단일 답변을 공감할만큼 충분한 평판을 가지고 있다고 생각하지 않습니다. 아웃 바운드 링크를 가진 모든 SO 사용자의 모든 답변은 물론 :-))
Franci Penov

2
아마도 나는 지나치게 간결했을 것입니다. 아마도 분명히해야합니다. 나는 링크를 반대하지 않습니다. 링크 만 포함 된 답변에 반대합니다. 나는 그것이 대답이라고 생각하지 않습니다. 이제 링크 된 내용이 적용되는 방식을 요약하기 위해 간단한 답변 요약을 게시 한 경우 허용됩니다. 게다가, 나는 같은 문제에 대한 답을 찾기 위해 여기에 왔습니다.이 답변은 특정 문제와 관련하여 말할 수있는 아이디어를 얻기 위해 클릭 해야하는 또 다른 링크이기 때문에 나를 화나게했습니다. 어쨌든 Jon Skeet은 이것과 어떤 관련이 있습니까? 왜 내가 신경 써야합니까?
Jon Davis

8
"게시 된 지 2 년이 지난 지금이 ​​게시물을 방문했으며 여기에서 복사 한 모든 내용이 더 이상 사용되지 않았을 수 있습니다." 링크가 될 수도 있습니다. 간결하지만 링크를 게시 할 때 완전한 요약을 게시하면 링크가 오래되거나 죽었는지 알 수 없습니다.
Jon Davis

2
나는 스티프 피에 동의하지 않는다. 실행 불가능 성으로 인해 수많은 정보를 포함하는 게시물에 대한 아이디어가 아니거나 누군가 이것을 불러 내지 않는다. 나는 말하고 싶지만 하지만, 가능성이 링크가 내용보다는 작동하지 않을 것을 사용되지 않는 /이 생략된다. 따라서 행사가 허락 할 때 더 많은 콘텐츠가 좋습니다. 우리는 모두 (주로) 자원 봉사자입니다. 감사합니다. Franci :)
zanlok

14

이 무료 전자 책을 읽는 것이 좋습니다. Josephing by Threading in C #

최소한 "시작하기"섹션을 읽으십시오. 전자 책은 훌륭한 소개를 제공하며 풍부한 고급 스레딩 정보도 포함합니다.

스레드 풀 사용 여부를 아는 것은 시작에 불과합니다. 다음으로 요구에 가장 적합한 스레드 풀을 입력하는 방법을 결정해야합니다.

  • 작업 병렬 라이브러리 (.NET Framework 4.0)
  • ThreadPool.QueueUserWorkItem
  • 비동기 대리인
  • BackgroundWorker

이 전자 책은이 모든 것을 설명하고 그것들을 언제 사용할 것인지 조언하고 나만의 스레드를 만듭니다.


8

스레드 풀은 스레드 간의 컨텍스트 전환을 줄 이도록 설계되었습니다. 여러 구성 요소가 실행중인 프로세스를 고려하십시오. 이러한 각 구성 요소는 작업자 스레드를 생성 할 수 있습니다. 프로세스에서 스레드가 많을수록 컨텍스트 전환에 더 많은 시간이 낭비됩니다.

이제 각 구성 요소가 스레드 풀에 항목을 큐에 넣는 경우 컨텍스트 전환 오버 헤드가 훨씬 줄어 듭니다.

스레드 풀은 CPU (또는 CPU 코어)에서 수행되는 작업을 최대화하도록 설계되었습니다. 이것이 기본적으로 스레드 풀이 프로세서 당 여러 스레드를 스핀 업하는 이유입니다.

스레드 풀을 사용하지 않으려는 상황이 있습니다. I / O를 기다리거나 이벤트를 기다리는 경우 해당 스레드 풀 스레드를 묶어 다른 사람이 사용할 수 없습니다. 장기 실행 작업을 구성하는 것이 주관적이지만 동일한 아이디어가 장기 실행 작업에 적용됩니다.

Pax Diablo도 좋은 지적입니다. 실을 회전시키는 것은 자유롭지 않습니다. 시간이 걸리고 스택 공간을 위해 추가 메모리를 소비합니다. 스레드 풀은 스레드를 재사용하여이 비용을 상각합니다.

참고 : 스레드 풀 스레드를 사용하여 데이터를 다운로드하거나 디스크 I / O를 수행하는 방법에 대해 질문했습니다. 이를 위해 스레드 풀 스레드를 사용해서는 안됩니다 (위에 설명한 이유로). 대신 비동기 I / O (일명 BeginXX 및 EndXX 메소드)를 사용하십시오. A의 FileStream그 것 BeginRead하고 EndRead. 들어 HttpWebRequest있을 것이라고 BeginGetResponse하고 EndGetResponse. 사용하기가 더 복잡하지만 멀티 스레드 I / O를 수행하는 올바른 방법입니다.


1
ThreadPool은 영리한 자동화입니다. "대기열이 0.5 초 이상 정지 상태를 유지하면 스레드 풀의 용량까지 0.5 초마다 하나씩 더 많은 스레드를 작성하여 응답합니다"( albahari.com/threading/#_Optimizing_the_Thread_Pool ). 또한 BeginXXX-EndXXX를 사용한 거의 비동기 작업은 ThreadPool을 통해 사용됩니다. 따라서 ThreadPool을 사용하여 데이터를 다운로드하고 암시 적으로 사용하는 것이 일반적입니다.
Artru

6

스레드 기아가 발생하기 쉬우므로 처리 과정에서 중요하거나 가변적이거나 알려지지 않은 부분을 차단할 수있는 작업에 대해서는 .NET 스레드 풀에주의하십시오. 스레드 작업에 대해 많은 논리적 추상화를 제공하는 .NET 병렬 확장 사용을 고려하십시오. ThreadPool에서 개선되어야 할 새로운 스케줄러도 포함되어 있습니다. 여기를 참조 하십시오


2
우리는 이것을 어려운 방법으로 발견했습니다! ASP.Net을 사용하는 Threadpool이 나타나므로 원하는만큼 적극적으로 사용할 수 없습니다.
noocyte 2013 년

3

소규모 작업에만 스레드 풀을 사용하는 한 가지 이유는 제한된 수의 스레드 풀 스레드가 있기 때문입니다. 하나를 오랫동안 사용하면 해당 스레드가 다른 코드에서 사용되지 않습니다. 이 문제가 여러 번 발생하면 스레드 풀이 모두 소모 될 수 있습니다.

스레드 풀을 사용하면 미묘한 영향을 줄 수 있습니다. 예를 들어 일부 .NET 타이머는 스레드 풀 스레드를 사용하며 실행되지 않습니다.


2

응용 프로그램의 전체 수명과 같이 오랜 시간 동안 지속되는 백그라운드 작업이있는 경우 자체 스레드를 만드는 것이 합리적입니다. 스레드에서 수행해야하는 짧은 작업이있는 경우 스레드 풀링을 사용하십시오.

많은 스레드를 작성하는 응용 프로그램에서는 스레드 작성의 오버 헤드가 상당히 커집니다. 스레드 풀을 사용하면 스레드가 한 번 작성되어 재사용되므로 스레드 작성 오버 헤드가 발생하지 않습니다.

내가 작업 한 응용 프로그램에서 스레드를 만드는 것에서 짧은 수명의 스레드에 대한 스레드 풀을 사용하는 것으로 변경하면 실제로 응용 프로그램을 통과시키는 데 도움이되었습니다.


"스레드 풀"또는 "스레드 풀"을 의미하는지 확인하십시오. 이것들은 매우 다릅니다 (적어도 MS CLR에서는).
bzlm

2

동시 실행 단위로 최고의 성능을 얻으려면 시작시 스레드 객체 풀이 생성되고 차단 (이전 일시 중단)으로 이동하여 컨텍스트 실행 대기 (표준 인터페이스가 구현 된 객체) 인 자체 스레드 풀을 작성하십시오. 귀하의 코드).

Tasks vs. Threads vs. .NET ThreadPool에 대한 많은 기사가 실제로 성능 결정에 필요한 것을 제시하지 못했습니다. 그러나 그것들을 비교하면 스레드가 승리하고 특히 스레드 풀이 나옵니다. CPU 전체에 가장 잘 분산되어 더 빨리 시작됩니다.

논의해야 할 것은 Windows의 주요 실행 단위 (Windows 10 포함)가 스레드이며 OS 컨텍스트 전환 오버 헤드가 무시할 수 있다는 사실입니다. 간단히 말해서, 기사가 컨텍스트 전환을 저장하여 더 높은 성능을 요구하는지 또는 더 나은 CPU 사용을 요구하는지에 관계없이 이러한 기사 중 많은 기사에 대한 확실한 증거를 찾지 못했습니다.

이제 약간의 사실감이 있습니다.

우리 대부분은 응용 프로그램이 결정론적일 필요가 없으며, 대부분 운영 체제 개발과 함께 제공되는 스레드에 대한 노크 배경이 없습니다. 위에서 쓴 것은 초보자를위한 것이 아닙니다.

가장 중요한 것은 토론하기가 프로그래밍하기 쉬운 것입니다.

자체 스레드 풀을 만들면 실행 상태 추적, 일시 중단 및 다시 시작을 시뮬레이션하는 방법 및 응용 프로그램 전체를 포함하여 실행을 취소하는 방법과 관련하여 작성해야 할 내용이 약간 있습니다. 닥쳐. 또한 풀을 동적으로 확장 할 것인지와 풀의 용량 제한에 대해 염려해야 할 수도 있습니다. 한 시간 안에 이러한 프레임 워크를 작성할 수 있지만 여러 번 수행했기 때문입니다.

실행 단위를 작성하는 가장 쉬운 방법은 작업을 사용하는 것입니다. 작업의 장점은 하나의 코드를 작성하여 코드에서 인라인으로 시작할 수 있다는 것입니다 (주의해야 할 수도 있음). 작업을 취소 할 때 처리 할 취소 토큰을 전달할 수 있습니다. 또한 체인 이벤트에 약속 방식을 사용하며 특정 유형의 값을 반환하도록 할 수 있습니다. 또한 async 및 await를 사용하면 더 많은 옵션이 존재하며 코드가 더 이식성이 뛰어납니다.

본질적으로 Tasks vs. Threads vs. .NET ThreadPool의 장단점을 이해하는 것이 중요합니다. 고성능이 필요한 경우 스레드를 사용하고 자체 풀을 사용하는 것을 선호합니다.

쉽게 비교할 수있는 방법은 512 스레드, 512 작업 및 512 ThreadPool 스레드를 시작하는 것입니다. Threads (왜 스레드 스레드를 작성해야하는지)로 시작하면 지연이 발생하지만 Tasks 및 .NET ThreadPool 스레드는 모두 시작하는 데 몇 분이 걸리지 만 몇 초 안에 512 개의 모든 스레드가 실행됩니다.

다음은 이러한 테스트 결과 (16GB RAM이 장착 된 i5 쿼드 코어)로 30 초마다 실행됩니다. 실행 된 코드는 SSD 드라이브에서 간단한 파일 I / O를 수행합니다.

시험 결과


1
참고로 Tasks 및 .NET Threads는 .NET 내에서 동시성이 시뮬레이션되고 OS가 아닌 .NET 내에서 관리가 실행됨을 언급하지 않았습니다. 후자는 동시 실행을 관리하는 데 훨씬 효율적입니다. 많은 작업에 Tasks를 사용하지만 과도한 실행 성능을 위해 OS Thread를 사용합니다. MS는 Tasks와 .NET Threads가 더 좋다고 주장하지만 일반적으로 .NET 앱 간의 동시성 균형을 유지하는 것이 좋습니다. 그러나 서버 앱은 OS가 동시성을 처리하도록하는 것이 가장 좋습니다.

사용자 정의 Threadpool의 구현을보고 싶습니다. 좋은 쓰기!
Francis

테스트 결과를 이해하지 못합니다. das "Units Ran"은 무슨 뜻입니까? 34 개의 탁과 512 개의 스레드를 비교합니까? 이것을 설명해 주시겠습니까?
Elmue

단위는 작업, 스레드 또는 .NET ThreadPool 작업자 스레드에서 동시에 실행하는 방법으로 시작 / 실행 성능을 비교 한 테스트입니다. 각 테스트에는 30 개의 스레드가 처음부터 512 개의 스레드를 생성하고, 512 개의 작업, 512 개의 ThreadPool 작업자 스레드가 생성되거나, 컨텍스트가 실행될 때까지 512 개의 시작된 스레드 풀이 재개됩니다. Tasks 및 ThreadPool 작업자 스레드의 회전 속도가 느리므로 30 초로 회전 할 수있는 시간이 충분하지 않습니다. 그러나 ThreadPool 최소 작업자 스레드 수를 처음 512로 설정하면 Tasks 및 ThreadPool 작업자 스레드가 모두 512 스레드만큼 빠르게 시작됩니다.


1

스레드 풀은 사용 가능한 스레드보다 처리 할 작업이 많을 때 유용합니다.

모든 작업을 스레드 풀에 추가하고 특정 시간에 실행할 수있는 최대 스레드 수를 지정할 수 있습니다.

MSDN 에서이 페이지를 확인하십시오 . http://msdn.microsoft.com/en-us/library/3dasc8as(VS.80).aspx


좋아, 이것은 내 다른 질문과 관련이 있다고 생각합니다. 주어진 시간에 사용 가능한 스레드 수를 어떻게 알 수 있습니까?

글쎄, 말하기 어렵다. 성능 테스트를 수행해야합니다. 포인트 후에 스레드를 더 추가해도 속도가 향상되지 않습니다. 기계에 몇 개의 프로세서가 있는지 알아보십시오. 좋은 출발점이 될 것입니다. 그런 다음 처리 속도가 향상되지 않으면 스레드를 더 추가하지 마십시오.
lajos

1

가능하면 최고 수준의 추상화에서 작업 할 수 있으면 항상 스레드 풀을 사용하십시오. 스레드 풀은 스레드 생성 및 파괴를 숨 깁니다. 일반적으로 좋은 일입니다!


1

스레드를 작성하는 고가의 프로세스를 피하면서 대부분의 경우 풀을 사용할 수 있습니다.

그러나 일부 시나리오에서는 스레드를 만들 수 있습니다. 예를 들어 스레드 풀을 사용하는 유일한 사람이 아니며 생성 한 스레드가 오래 지속되는 경우 (공유 리소스 소비를 피하기 위해) 또는 예를 들어 스레드의 스택 크기를 제어하려는 경우입니다.


1

백그라운드 작업자를 조사하는 것을 잊지 마십시오.

나는 많은 상황을 발견하고, 무거운 물건을 들지 않고 원하는 것을 제공합니다.

건배.


실행중인 간단한 앱이고 다른 작업을 수행해야 할 때이 코드를 매우 쉽게 수행 할 수 있습니다. 당신은 그래도 링크를 제공하지 않았습니다 : 사양튜토리얼
zanlok

0

나는 보통 다른 스레드에서 무언가를 할 필요가있을 때마다 Threadpool을 사용하며 실제로 실행되거나 종료 될 때 신경 쓰지 않습니다. 로깅이나 파일 다운로드와 같은 것 (비동기 스타일을 수행하는 더 좋은 방법이 있지만). 더 많은 제어가 필요할 때 내 스레드를 사용합니다. 또한 내가 찾은 것은 스레드 안전 대기열 (자신의 해킹)을 사용하여 "명령 객체"를 저장하는 것이> 1 스레드에서 작업 해야하는 여러 명령이있을 때 좋습니다. 따라서 Xml 파일을 분할하여 각 요소를 대기열에 넣은 다음 여러 요소를 처리하여 여러 요소를 처리 할 수 ​​있습니다. 나는 C #으로 변환 한 uni (VB.net!)로 돌아가는 대기열 방법을 썼습니다. 특별한 이유없이 아래에 포함 시켰습니다 (이 코드에는 약간의 오류가있을 수 있습니다).

using System.Collections.Generic;
using System.Threading;

namespace ThreadSafeQueue {
    public class ThreadSafeQueue<T> {
        private Queue<T> _queue;

        public ThreadSafeQueue() {
            _queue = new Queue<T>();
        }

        public void EnqueueSafe(T item) {
            lock ( this ) {
                _queue.Enqueue(item);
                if ( _queue.Count >= 1 )
                    Monitor.Pulse(this);
            }
        }

        public T DequeueSafe() {
            lock ( this ) {
                while ( _queue.Count <= 0 )
                    Monitor.Wait(this);

                return this.DeEnqueueUnblock();

            }
        }

        private T DeEnqueueUnblock() {
            return _queue.Dequeue();
        }
    }
}

이 접근 방식의 일부 문제점 :-DequeueSafe ()에 대한 호출은 항목이 EnqueuedSafe ()가 될 때까지 기다립니다. 시간 초과를 지정하여 Monitor.Wait () 오버로드 중 하나를 사용하십시오. -이것에 대한 잠금은 모범 사례에 따르지 않고 읽기 전용 객체 필드를 만듭니다. -Monitor.Pulse ()는 경량이지만 대기열에 항목이 하나만있을 때 호출하는 것이 더 효율적입니다. - DeEnqueueUnblock는 (), 양호하게 queue.Count> 0 (Monitor.PulseAll 또는 대기 시간 초과를 사용하는 경우 필요)를 확인한다
크레이그 니콜슨

0

가능한 한 짧은 대기 시간으로 코어 전체에 작업을 분산시키는 스레드 풀을 원했으며 다른 응용 프로그램과 잘 어울리지 않아도되었습니다. .NET 스레드 풀 성능이 좋지 않은 것으로 나타났습니다. 코어 당 하나의 스레드를 원한다는 것을 알고 있으므로 자체 스레드 풀 대체 클래스를 작성했습니다. 이 코드는 여기 다른 StackOverflow 질문에 대한 답변으로 제공됩니다 .

원래 질문에 관해서, 스레드 풀은 반복 계산을 병렬로 실행할 수있는 부분으로 나눌 때 유용합니다 (결과를 변경하지 않고 병렬로 실행할 수 있다고 가정). 수동 스레드 관리는 UI 및 IO와 같은 작업에 유용합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.