-
18-09-2019 - |
문제
문제
내가 여기서 이야기 할 코드는 F#에 썼지 만, f#의 특정성에 따라 .NET 4 프레임 워크를 기반으로합니다 (적어도 그렇게 보입니다!).
디스크에 네트워크에서 업데이트 해야하는 데이터 조각이 있습니다. 최신 버전을 디스크에 저장합니다.
type MyData =
{ field1 : int;
field2 : float }
type MyDataGroup =
{ Data : MyData[];
Id : int }
// load : int -> MyDataGroup
let load dataId =
let data = ... // reads from disk
{ Data = data;
Id = dataId }
// update : MyDataGroup -> MyDataGroup
let update dg =
let newData = ... // reads from the network and process
// newData : MyData[]
{ dg with Data = dg.Data
|> Seq.ofArray
|> Seq.append newData
|> processDataSomehow
|> Seq.toArray }
// save : MyDataGroup -> unit
let save dg = ... // writes to the disk
let loadAndSaveAndUpdate = load >> update >> save
문제는 loadAndSaveAndUpdate
내 모든 데이터, 나는 함수를 실행해야 할 것이다 많은 타임스:
{1 .. 5000} |> loadAndSaveAndUpdate
각 단계는 할 것입니다
- 일부 디스크 IO,
- 일부 데이터 크런치,
- 일부 네트워크 IO (많은 대기 시간 가능성),
- 더 많은 데이터 크런치,
- 그리고 일부 디스크 io.
이 작업을 어느 정도 동시에 수행하는 것이 좋지 않습니까? 불행히도, 내 독서 및 구문 분석 기능 중 어느 것도 "비동기식 플로우 준비"가 아닙니다.
내가 생각한 첫 번째 (아주 좋지 않은) 솔루션
작업
내가 한 첫 번째 일은 Task[]
그리고 그들 모두를 시작하십시오 :
let createTask id = new Task(fun _ -> loadAndUpdateAndSave id)
let tasks = {1 .. 5000}
|> Seq.map createTask
|> Seq.toArray
tasks |> Array.iter (fun x -> x.Start())
Task.WaitAll(tasks)
그런 다음 CTRL+ESC를 눌러 사용중인 스레드 수를 확인했습니다. 15, 17, ..., 35, ..., 170, ... 신청서를 죽일 때까지! 뭔가 잘못되고있었습니다.
평행한
나는 거의 똑같은 일을했지만 사용했습니다 Parallel.ForEach(...)
그리고 결과는 동일했다 : 많은 실, 많은 실.
작동하는 솔루션 ... 일종의
그런 다음 시작하기로 결정했습니다 n
스레드, Task.WaitAll(of them)
, 그럼 다른 n
, 더 이상 사용 가능한 작업이 없을 때까지.
이것은 작동하지만 문제는 처리가 완료되었을 때 n-1
작업, 기다릴 것입니다. 기다릴 것입니다. 많은 네트워크 대기 시간으로 인해 차단을 주장하는 마지막 작업이 기다릴 것입니다. 이것은 좋지 않습니다!
그래서, 이 문제를 어떻게 공격하겠습니까?? 비동기 워크 플로 (및이 경우 비 동기 기능을 조정하는 방법), 병렬 확장, 이상한 병렬 패턴 등과 관련된 다른 솔루션을 보는 것이 좋습니다.
감사.
해결책
개별 작업이 적시에 완료되고 있다고 확신합니까? 나는 둘 다 믿습니다 Parallel.ForEach
그리고 Task
클래스는 이미 .NET ThreadPool을 사용합니다. 작업은 일반적으로 수명이 짧은 작업 항목이어야하며,이 경우 ThreadPool은 소수의 실제 스레드 만 스폰되지만 작업이 진행되지 않고 다른 작업이 대기하는 경우 사용 된 스레드의 수는 꾸준히 증가합니다. 최대 값 (기본적으로 250/프로세서 .NET 2.0 SP1에서는 프레임 워크의 다른 버전에서 다릅니다). 또한 (적어도 .NET 2.0 SP1에서) 새 스레드 생성이 초당 2 개의 새로운 스레드로 조절되므로보고있는 스레드 수에 올라가는 것은 작업이 짧은 양으로 완료되지 않았 음을 나타냅니다. 시간 (그래서 비난을 고정하는 것이 완전히 정확하지 않을 수 있습니다. Parallel.ForEach
).
나는 브라이언의 사용 제안이라고 생각합니다 async
워크 플로우는 특히 장기적인 작업의 출처가 IO 인 경우 좋은 것입니다. async
IO가 완료 될 때까지 스레드를 스레드 풀로 되돌립니다. 또 다른 옵션은 작업이 빠르게 완료되지 않고 많은 스레드의 산란을 허용하는 것입니다 (사용하여 어느 정도 제어 할 수 있습니다. System.Threading.ThreadPool.SetMaxThreads
) - 상황에 따라 많은 스레드를 사용하는 것은 큰 문제가 아닐 수도 있습니다.
다른 팁
병렬 탑 병렬 메소드 호출에 의해 실행되는 동시 작업 수를 제한합니다.
'Async'를 사용하면 다양한 I/O 통화가 'Sea'인 동안 실을 태우지 않고 I/O 바운드 작업을 수행 할 수 있으므로 첫 번째 제안이 될 것입니다. 코드를 일반적으로 라인을 따라 비동기로 변환하는 것이 간단해야합니다.
- 각 기능 본문을 감습니다
async{...}
, 추가하다return
필요한 곳에 - 라이브러리를 통해 아직 라이브러리에 있지 않은 모든 I/O 프리미티브의 비동기 버전 생성
Async.FromBeginEnd
- 양식의 호출을 전환합니다
let r = Foo()
에게let! r = AsyncFoo()
- 사용
Async.Parallel
5000 개의 비동기 물체를 병렬로 실행되는 단일 비동기로 변환하려면
이를위한 다양한 튜토리얼이 있습니다. 그러한 웹 캐스트 중 하나입니다 여기.
항상 사용할 수 있습니다 ThreadPool
.
http://msdn.microsoft.com/en-us/library/system.threading.threadpool.aspx
원래:
- 스레드 풀을 만듭니다
- 최대 스레드 수를 설정하십시오
- 사용하는 모든 작업을 대기하십시오
QueueUserWorkItem(WaitCallback)