एक लंबे समय तक चलने वाली Foundation OperationQueue का उपयोग करें और गणनाएँ सबमिट करने से पहले उसका maxConcurrentOperationCount सेट करें। हर बटन दबाने पर एक अनुरोध पाने वाली Mac उपयोगिता में, इससे एक साथ चल रहे काम की सीमा तय होती है और बाद के अनुरोध प्रतीक्षा कर सकते हैं।
यह ट्यूटोरियल उस डेस्कटॉप कार्य के लिए गणना इंजन और उसके व्यवहार की जाँच करने वाला एक स्वतंत्र प्रोग्राम बनाता है। हर अनुरोध में कृत्रिम पूर्णांक होते हैं और हर परिणाम मेमोरी में रहता है। गणना उसके ऑपरेशन के भीतर समकालिक रूप से चलती है, इसलिए ऑपरेशन का जीवनकाल वास्तविक काम को समेटे रहता है।
अलग-अलग आने वाले अनुरोधों के लिए कौन-सा समवर्ती टूल उपयुक्त है?
OperationQueue अलग-अलग ऑपरेशन स्वीकार करता है, और उसकी ब्लॉक-आधारित addOperation(_:) विधि हर सबमिट किए गए क्लोज़र को एक ऑपरेशन में लपेटती है। क्यू को ऐसे ऑब्जेक्ट में रखें जो अनुरोधों के बीच बना रहे, फिर आने वाले हर आइटम के लिए एक क्लोज़र सबमिट करें। Apple का addOperation(_:) दस्तावेज़ इस सबमिशन विधि का वर्णन करता है।
maxConcurrentOperationCount को 2 जैसी धनात्मक सीमा पर सेट करें। Apple इस प्रॉपर्टी को उस क्यू के उन ऑपरेशनों की अधिकतम संख्या के रूप में परिभाषित करता है जो एक साथ चल सकते हैं। यह सीमा उसी खास क्यू पर लागू होती है; हर अनुरोध के लिए अलग क्यू बनाने से साझा सीमा निष्प्रभावी हो जाएगी। इसे बाद में घटाने पर पहले से चल रहे ऑपरेशन चलते रहेंगे। Apple का समवर्ती-सीमा संबंधी दस्तावेज़ इन सीमाओं की व्याख्या करता है।
Swift टास्क ग्रुप का जीवनकाल अलग होता है। यह withTaskGroup स्कोप के भीतर होता है, इसे बनाने वाले टास्क के बाहर इस्तेमाल नहीं किया जा सकता और लौटने से पहले यह अपने चाइल्ड टास्क के पूरा होने की प्रतीक्षा करता है। इसलिए, भविष्य में होने वाले अलग-अलग बटन दबाने के लिए टास्क ग्रुप को प्रॉपर्टी के रूप में रखना समर्थित नहीं है। इस समकालिक गणना इंजन के लिए, लंबे समय तक बनाए रखा गया ऑपरेशन क्यू आवश्यक जीवनकाल और स्पष्ट समवर्ती सेटिंग देता है। Swift की मानक लाइब्रेरी के टास्क-ग्रुप दस्तावेज़ इन पाबंदियों का वर्णन करते हैं।
| आपके काम का तरीका | उपयुक्त तरीका |
|---|---|
| स्वतंत्र समकालिक गणनाएँ अलग-अलग आती हैं | एक OperationQueue को बनाए रखें और हर गणना के लिए एक ऑपरेशन सबमिट करें। |
| कोई पैरेंट असिंक्रोनस ऑपरेशन चाइल्ड टास्क का एक सीमित समूह संभालता है | उस पैरेंट के स्कोप में टास्क ग्रुप का उपयोग करें; यदि संख्यात्मक सीमा चाहिए तो टास्क जोड़ने की प्रक्रिया को स्पष्ट रूप से नियंत्रित करें। |
| काम को डेस्कटॉप इंटरफ़ेस अपडेट करना है | गणना अलग से करें, फिर उसका परिणाम MainActor से अलग किए गए कोड तक पहुँचाएँ। |
आखिरी पंक्ति परिणाम पहुँचाने के बारे में है। Swift की भाषा मार्गदर्शिका इंटरफ़ेस की स्थिति के लिए मुख्य ऐक्टर को उपयुक्त स्थान बताती है और स्पष्ट रूप से main-actor-isolated क्लोज़र के उदाहरण देती है। महँगी गणना उस परिणाम-पहुँचाने वाले चरण से बाहर रखें। The Swift Programming Language: Concurrency main-actor isolation की व्याख्या करता है।
गणना और उसके पूरा होने की शर्त तय करें
यह उदाहरण 1 से 1_000_000 तक की सीमा में एक पूर्णांक पहचानकर्ता और एक पुनरावृत्ति संख्या स्वीकार करता है। n पुनरावृत्तियों के लिए, यह 1 से n तक के सभी पूर्णांकों के वर्ग जोड़ता है। यह पहचानकर्ता के साथ UInt64 परिणाम लौटाता है। पहचानकर्ता आउटपुट को नाम देता है; इससे अंकगणित प्रभावित नहीं होता।
इस प्रोग्राम के लिए चुनी गई ये उदाहरणात्मक सीमाएँ हैं। अधिकतम स्वीकार्य इनपुट पर परिणाम 333333833333500000 होता है, जो UInt64 में समा जाता है। सबमिशन से पहले सत्यापन होता है, इसलिए अमान्य अनुरोध तुरंत त्रुटि देता है और कोई ऑपरेशन क्यू में नहीं जुड़ता। इनपुट की पाबंदी से लूप की रेंज भी मान्य रहती है।
हर स्वीकार किए गए अनुरोध के ऑपरेशन के पूरा होने पर एक परिणाम संग्रहित होता है। परिणाम एक ऐरे में जुड़ते हैं, इसलिए एक ही पहचानकर्ता दोबारा आने पर पहले का परिणाम मिटता नहीं है। प्रदर्शन में अलग-अलग बढ़ते हुए पहचानकर्ता इस्तेमाल होते हैं और पठनीयता के लिए पहचानकर्ता के अनुसार क्रम लगाया जाता है; यह प्रदर्शन क्रम निष्पादन क्रम के बारे में कोई दावा नहीं करता।
यह शर्त मानती है कि क्यू उपलब्ध रहता है और उसके ऑपरेशन पूरे होते हैं। उदाहरण में रद्द करने की सुविधा नहीं है। बाद में रद्द करने की सुविधा जोड़ने के लिए अधूरे अनुरोधों का अलग परिणाम तय करना होगा, क्योंकि Apple के दस्तावेज़ के अनुसार रद्द करना रुकने का अनुरोध है और चल रहे काम को उस अनुरोध का जवाब देना होता है। Apple का OperationQueue दस्तावेज़ ऑपरेशन के जीवनकाल और रद्द करने की व्याख्या करता है।
साझा सीमा वाला पूरा प्रोग्राम बनाएँ
Swift 6.2 या नए कंपाइलर वाला Mac इस्तेमाल करें और Xcode या उसके कमांड-लाइन टूल के ज़रिए उपलब्ध macOS SDK रखें। नीचे दिया गया पूरा प्रोग्राम main.swift के रूप में सेव करें। नीचे दिए गए कमांड स्पष्ट रूप से nonisolated डिफ़ॉल्ट isolation चुनते हैं, जिससे गणना हेल्पर किसी ऐप टारगेट की isolation सेटिंग से स्वतंत्र रहते हैं। Swift इस कंपाइलर सेटिंग का दस्तावेज़ SE-0466: Control default actor isolation inference में देता है।
परिणाम लॉग अपने ऐरे और गतिविधि काउंटर, दोनों की सुरक्षा के लिए NSLock का उपयोग करता है। इसकी @unchecked Sendable conformance के तहत इस सिंक्रोनाइज़ेशन की ज़िम्मेदारी कार्यान्वयन पर है: म्यूटेबल स्थिति के हर ऐक्सेस में वही लॉक लिया जाता है। Apple इस ज़िम्मेदारी का वर्णन Sendable में और क्रिटिकल सेक्शन की सुरक्षा का वर्णन NSLock में करता है।
import Foundation
enum InputError: Error {
case invalidIterations
}
struct Calculation: Sendable {
let id: Int
let iterations: Int
}
func sumOfSquares(_ job: Calculation) -> UInt64 {
var sum: UInt64 = 0
for integer in 1...job.iterations {
let value = UInt64(integer)
sum += value * value
}
return sum
}
final class ExecutionLog: @unchecked Sendable {
private let lock = NSLock()
private var active = 0
private var peak = 0
private var rows: [(id: Int, value: UInt64)] = []
func begin() {
lock.lock()
defer { lock.unlock() }
active += 1
peak = max(peak, active)
}
func finish(id: Int, value: UInt64) {
lock.lock()
defer { lock.unlock() }
rows.append((id: id, value: value))
active -= 1
}
func snapshot() -> (
rows: [(id: Int, value: UInt64)], peak: Int
) {
lock.lock()
defer { lock.unlock() }
return (rows, peak)
}
}
final class CalculationPool {
private let queue = OperationQueue()
private let log = ExecutionLog()
init(limit: Int) {
precondition(limit > 0)
queue.name = "Synthetic calculations"
queue.maxConcurrentOperationCount = limit
}
func submit(id: Int, iterations: Int) throws {
guard (1...1_000_000).contains(iterations) else {
throw InputError.invalidIterations
}
let job = Calculation(id: id, iterations: iterations)
queue.addOperation { [log] in
log.begin()
let value = sumOfSquares(job)
log.finish(id: job.id, value: value)
}
}
// Call only after the producer has stopped submitting work.
// This blocking method is for the command-line demonstration.
func finishForDemo() -> (
rows: [(id: Int, value: UInt64)], peak: Int
) {
queue.waitUntilAllOperationsAreFinished()
return log.snapshot()
}
}
@main
struct Demo {
static func main() throws {
let limit = 2
let pool = CalculationPool(limit: limit)
try pool.submit(id: 1, iterations: 100_000)
try pool.submit(id: 2, iterations: 200_000)
try pool.submit(id: 3, iterations: 300_000)
let report = pool.finishForDemo()
let rows = report.rows.sorted { $0.id < $1.id }
precondition(rows.map { $0.id } == [1, 2, 3])
precondition(rows.map { $0.value } == [
UInt64(333338333350000),
UInt64(2666686666700000),
UInt64(9000045000050000)
])
precondition(report.peak >= 1 && report.peak <= limit)
for row in rows {
print("Job \(row.id): \(row.value)")
}
print("Observed peak: \(report.peak)")
}
}
main.swift वाली डायरेक्टरी से इसे कंपाइल और चलाएँ:
swiftc -swift-version 6 -default-isolation nonisolated \
-parse-as-library main.swift -o calculation-demo
./calculation-demo
परिणाम की तीन पंक्तियाँ ये होनी चाहिए:
Job 1: 333338333350000
Job 2: 2666686666700000
Job 3: 9000045000050000
आखिरी पंक्ति देखी गई अधिकतम संख्या 1 या 2 में से एक बताती है। सीमा एक साथ चलने की अनुमति देती है; यह इसकी गारंटी नहीं देती। अपेक्षित योगों की स्वतंत्र रूप से पूर्णांक अंकगणित और सूत्र n * (n + 1) * (2 * n + 1) / 6 से जाँच की गई। Swift प्रोग्राम का निरीक्षण किया गया, लेकिन शोध वातावरण में Swift निष्पादन योग्य इंस्टॉल न होने के कारण उसे कंपाइल नहीं किया जा सका।
यह सीमा वास्तविक गणना पर कैसे लागू होती है?
हर ऑपरेशन अपना गतिविधि काउंटर शुरू करता है, पूरी गणना समकालिक रूप से करता है, परिणाम दर्ज करता है और फिर लौटता है। उस क्लोज़र से कोई अतिरिक्त असिंक्रोनस गणना बाहर नहीं निकलती। इसलिए, चल रहे ऑपरेशनों की संख्या सीमित करने से इन गणनाओं की संख्या भी सीमित होती है।
प्रोग्राम को अनुकूलित करते समय यह संबंध बनाए रखें। यदि कोई ऑपरेशन केवल एक अलग Task बनाता है और तुरंत लौट जाता है, तो क्यू ऑपरेशन को पूरा मान लेता है, जबकि वह टास्क गणना जारी रख सकता है। तब ऑपरेशन की सीमा केवल उन छोटे लॉन्च क्लोज़रों पर लागू होगी। यह ऑपरेशन के जीवनकाल और ब्लॉक सबमिशन API के दस्तावेज़ित व्यवहार से निकलता है। Apple का OperationQueue परिचय और addOperation(_:) इन पहलुओं को परिभाषित करते हैं।
लॉग का लॉक केवल छोटे हिसाब-किताब वाले हिस्सों को घेरे रहता है। sumOfSquares(_:) के दौरान इसे पकड़े रखने से गणना तक पहुँच क्रमबद्ध हो जाएगी और इच्छित ओवरलैप रुक जाएगा। हर लॉक को लेना और छोड़ना एक ही समकालिक विधि में होता है, जो Apple की इस आवश्यकता के अनुरूप है कि NSLock को उसी थ्रेड पर अनलॉक किया जाए जिसने उसे लॉक किया था। Apple का NSLock दस्तावेज़ इस आवश्यकता की व्याख्या करता है।
अपने बदलाव की जाँच के लिए, उन्हीं तीन अनुरोधों को चलाएँ और limit को 1 पर सेट करें, फिर 2 बहाल करें। दोनों रन में तीनों सटीक परिणाम आने चाहिए। सीरियल रन में अधिकतम संख्या 1 होनी चाहिए; दूसरे रन में यह 2 के भीतर रहनी चाहिए। ये जाँचें किसी खास शेड्यूलिंग क्रम को माने बिना परिणामों के सुरक्षित रहने और देखी गई गणना सीमा की पुष्टि करती हैं।
इंटरफ़ेस को रोके बिना डेस्कटॉप क्रियाओं से जोड़ें
डेस्कटॉप गणना सुविधा के स्वामी घटक में एक CalculationPool बनाए रखें। हर आने वाली क्रिया submit(id:iterations:) को एक बार कॉल करे। क्रिया हैंडलर के भीतर पूल दोबारा न बनाएँ, क्योंकि हर नया इंस्टेंस अपनी अलग क्यू और सीमा रखता है।
डेमो अंतिम सबमिशन के बाद ही finishForDemo() कॉल करता है, ताकि कमांड-लाइन प्रक्रिया बाहर निकलने से पहले परिणाम जमा कर सके। यह विधि कॉल करने वाले थ्रेड को रोकती है। डेस्कटॉप ऐप में बटन हैंडलर या मुख्य ऐक्टर से इसे कॉल करने के बजाय हर परिणाम को अलग से पहुँचाएँ। Apple waitUntilAllOperationsAreFinished() के ब्लॉक करने वाले व्यवहार का स्पष्ट रूप से दस्तावेज़ देता है।
इंटरफ़ेस के साथ जोड़ने के लिए, सबमिशन API में MainActor से अलग किया गया और @Sendable चिह्नित परिणाम कॉलबैक जोड़ें। ऑपरेशन के भीतर परिणाम की गणना के बाद, कॉलबैक को लागू करने के लिए स्पष्ट @MainActor क्लोज़र वाला Task शेड्यूल करें। अपरिवर्तनीय परिणाम मान और कॉलबैक कैप्चर करें। उस क्लोज़र के भीतर केवल डिस्प्ले अपडेट रखें; अंकगणित ऑपरेशन में ही रहने दें। Swift main-actor क्लोज़र सिंटैक्स और sendable क्लोज़र कैप्चर, दोनों का दस्तावेज़ देता है।
इंटरफ़ेस वाले इस रूप में पूरा होने की एक और परिभाषा चाहिए: गणना पूरी होना और उसका परिणाम दिखना अलग-अलग घटनाएँ हैं। कमांड-लाइन प्रोग्राम गणनाओं और परिणामों के समकालिक संग्रह का इंतज़ार करता है। अलग से शेड्यूल किया गया इंटरफ़ेस कॉलबैक उसके बाद चल सकता है।
जब सबमिशन गणनाओं से तेज़ हों तो क्या होता है?
समवर्ती सेटिंग चल रहे ऑपरेशनों को सीमित करती है, जबकि सबमिट किया गया अतिरिक्त काम क्यू में प्रतीक्षा कर सकता है। इससे उदाहरण के पूरे हुए परिणामों का ऐरे भी बढ़ता रह सकता है। इसलिए यह उदाहरण गणनाओं की समवर्ती संख्या सीमित करता है, लेकिन कुल मेमोरी या बैकलॉग की सीमा नहीं देता। Apple दस्तावेज़ देता है कि क्यू ऑपरेशन पूरे होने तक उन्हें अपने पास रखता है। OperationQueue का परिचय इसे समझाता है।
यदि डेस्कटॉप सुविधा में लगातार काम के बड़े झोंके आ सकते हैं, तो इसे बढ़ाने से पहले अनुरोध स्वीकार करने की नीति चुनें: केवल निश्चित संख्या में लंबित अनुरोध स्वीकार करें और बाकी को अस्वीकार करें, आगे की सबमिशन रोकें, या नया अनुरोध स्पष्ट रूप से वांछित होने पर लंबित कृत्रिम प्रीव्यू को उससे बदल दें। स्वीकार किए गए अनुरोधों की संख्या और पूरा होने के अपडेट एक ही सिंक्रोनाइज़्ड स्वामी के ज़रिए संभालें। केवल चल रहे काम की सीमा इन नीतियों को लागू नहीं कर सकती।
अलग-अलग समकालिक अनुरोधों के लिए ज़रूरी डिज़ाइन छोटा है: एक क्यू बनाए रखें, उसकी सीमा एक बार सेट करें, हर गणना को उसके ऑपरेशन के भीतर रखें और साझा परिणामों को सिंक्रोनाइज़ करें। डेस्कटॉप क्रियाओं से जोड़ते समय इन सीमाओं को बनाए रखें, फिर आगमन के तरीके की ज़रूरत पड़ने पर ही स्पष्ट बैकलॉग नीति जोड़ें।
स्रोत
- OperationQueue — Apple।
- maxConcurrentOperationCount — Apple।
- addOperation(_:) — Apple।
- waitUntilAllOperationsAreFinished() — Apple।
- NSLock — Apple।
- Sendable — Apple।
- Concurrency — The Swift Programming Language — Swift.org।
- TaskGroup.swift — Swift standard-library source and documentation — Swift.org।
- SE-0466: Control default actor isolation inference — Swift.org।