세마포어의 최대 값?
예를 들어 1000 회 루프가 있습니다. 빠르고 효과적이며 교착 상태로 이어지지 않는 최대 값은 얼마입니까?
let group = DispatchGroup()
let queue = DispatchQueue(label: "com.num.loop", attributes: .concurrent)
let semaphore = DispatchSemaphore(value: 4)
for i in 1...1000 {
semaphore.wait()
group.enter()
queue.async(group: group, execute: {
doWork(i)
group.leave()
semaphore.signal()
})
}
group.notify(queue: DispatchQueue.main) {
// go on...
}
답변
몇 가지 관찰 :
QoS 당 최대 GCD 작업자 스레드 수를 초과하고 싶지 않습니다. 이 값을 초과하면 앱 내에서 차단이 발생할 수 있습니다. 마지막으로 확인한이 제한은 64 스레드였습니다.
즉, 일반적으로 장치의 코어 수를 초과하는 이점은 거의 없습니다.
종종 우리는 GCD가를 사용하여 최대 동시 스레드 수를 알아 내도록하는데 concurrentPerform, 이는 장치에 자동으로 최적화됩니다. 또한 세마포어 또는 그룹이 필요하지 않으므로 코드가 덜 복잡해집니다.
DispatchQueue.global().async { DispatchQueue.concurrentPerform(iterations: 1000) { i in doWork(i) } DispatchQueue.main.async { // go on... } }는
concurrentPerform병렬로 1,000 반복을 실행하지만, 세마포어에 대한 필요성을 제거 장치에 대한 레벨 적절한 동시 스레드의 수를 제한합니다. 그러나concurrentPerform자체적으로 동기식이며 모든 반복이 완료 될 때까지 진행되지 않으므로 디스패치 그룹이 필요하지 않습니다. 따라서 전체concurrentPerform를 일부 백그라운드 큐로 전달하고 완료되면 "완료 코드"를 수행합니다 (또는 해당 코드를 다시 기본 큐로 전달).concurrentPerform위에서 주장했지만doWork작업을 동 기적으로 수행하는 경우에만 작동합니다 (예 : 일부 컴퓨팅 작업). 그 자체가 비동기적인 무언가를 시작한다면, 우리는이 세마포어 / 그룹 기술로 돌아 가야합니다. (또는 더 나은 방법 Operation은 합리적인 큐가있는 비동기 서브 클래스를 사용 maxConcurrentOperationCount하거나 flatMap(maxPublishers:_:)개수에 대한 합리적인 제한이있는 결합을 사용하는 것입니다).이 경우 합리적인 임계 값에 관해서는 마법의 숫자가 없습니다. 몇 가지 경험적 테스트를 수행하여 코어 수와 앱 내에서 발생할 수있는 다른 작업 간의 적절한 균형을 찾아야합니다. 예를 들어, 네트워크 요청의 경우, 우리는 종종 4 개 또는 6 개를 최대 개수로 사용합니다. 이는 해당 개수를 초과 할 때의 이점 감소뿐 아니라 수천 명의 사용자가 동시에 너무 많은 파일을 제출하는 경우 서버에 미치는 영향을 고려합니다. 동시에 요청합니다.
"빠르게 만들기"측면에서 "동시에 실행할 수있는 반복 횟수"를 선택하는 것은 의사 결정 프로세스의 일부일뿐입니다. 더 중요한 문제
doWork는 동시 패턴으로 인해 발생하는 적당한 오버 헤드를 정당화하는 데 충분한 작업을 수행 하는 것을 빠르게 보장합니다 .예를 들어 1,000x1,000 픽셀의 이미지를 처리하는 경우 1,000,000 회 반복을 수행 할 수 있으며 각 반복은 1 픽셀입니다. 그러나 그렇게하면 실제로는 동시 변환이 아닌 변환보다 느리다는 것을 알 수 있습니다. 대신 1,000 개의 반복이있을 수 있으며 각 반복은 1,000 픽셀을 처리합니다. 또는 각각 10,000 픽셀을 처리하는 100 번의 반복이있을 수 있습니다. "striding"이라고하는이 기술은 수행 할 반복 횟수와 각각에 대해 수행되는 작업량 간의 적절한 균형을 찾기 위해 약간의 경험적 연구가 필요한 경우가 많습니다. (그런데 종종 이러한 스 트레이딩 패턴은 여러 스레드가 인접한 메모리 주소를 놓고 경쟁 할 때 발생할 수있는 시나리오 인 캐시 슬로 싱을 방지 할 수도 있습니다.)
이전 요점과 관련하여 우리는 종종 이러한 다양한 스레드가 공유 리소스에 대한 액세스를 동기화하기를 원합니다 (스레드 안전을 유지하기 위해). 이러한 동기화로 인해 이러한 스레드간에 경합이 발생할 수 있습니다. 따라서이 동기화를 수행하는 방법과시기에 대해 생각하고 싶을 것입니다.
예를 들어에서 여러 동기화를 수행하는 대신
doWork각 반복에서 로컬 변수를 업데이트하고 (동기화가 필요하지 않은 경우) 로컬 계산이 완료된 경우에만 공유 리소스에 대한 동기화 된 업데이트를 수행 할 수 있습니다. 이 질문은 무엇doWork을하고 있는지에 따라 크게 달라 지므로 초록으로 답하기는 어렵지만 전반적인 성능에 쉽게 영향을 미칠 수 있습니다.