- Zig भाषा ने async I/O के पुराने डिज़ाइन की जटिलता कम करने के लिए नए
Ioइंटरफेस-आधारित मॉडल को अपनाया है - यह मॉडल सिंक्रोनस और असिंक्रोनस कोड को अलग किए बिना समान फंक्शन स्ट्रक्चर बनाए रखता है, और
Io.ThreadedतथाIo.Eventedदो implementations प्रदान करता है Io.Threadedडिफ़ॉल्ट रूप से सिंक्रोनस रन करता है, जबकिIo.Eventedइवेंट-लूप आधारित असिंक्रोनस रन करता है- डेवलपर
async()औरconcurrent()फंक्शनों के ज़रिए पैरलल रन नियंत्रण कर सकते हैं और बिना कोड बदले performance ऑप्टिमाइज़ कर सकते हैं - यह approach function coloring समस्या को सुलझाते हुए Zig की सादगी और नियंत्रण बनाए रखते हुए async performance सुरक्षित करने की दिशा में है
Zig की async डिजाइन में बदलाव
- Zig ने पाया कि पहले का async डिजाइन भाषा के minimalism philosophy के साथ सही मेल नहीं खाता था, इसलिए एक नया approach खोजा
- पुराने डिजाइन की अन्य फीचर्स के साथ एकीकरण क्षमता कम थी
- नया मॉडल सिंक्रोनस और असिंक्रोनस I/O को एक जैसी code structure से हैंडल करने में सक्षम है
- नया डिजाइन
Iogeneric interface के आसपास काम करता है- सभी I/O फंक्शन
Ioinstance को parameter के रूप में लेकर रन होते हैं Allocatorइंटरफेस जैसा ढांचा, यानी memory allocation की तरह I/O control करना संभव है
- सभी I/O फंक्शन
Io इंटरफेस की संरचना
- स्टैंडर्ड लाइब्रेरी में दो बुनियादी implementations शामिल हैं
Io.Threaded: डिफ़ॉल्ट रूप से सिंक्रोनस रन, ज़रूरत पड़ने पर thread-based parallel handlingIo.Evented: इवेंट-लूप आधारित असिंक्रोनस रन (io_uring,kqueueआदि का उपयोग)
- यूज़र्स सीधे नया
Ioimplementation लिख सकते हैं, जिससे execution mode पर fine-grained control मिलता है
कोड उदाहरण और काम करने का तरीका
- उदाहरण फ़ंक्शन
saveFile()फ़ाइल बनाता, लिखता और बंद करता हैIo.Threadedके साथ सामान्य system calls की तरह रन होता हैIo.Eventedके साथ असिंक्रोनस backend पर रन होता है- दोनों मामलों में
writeAll()कॉल करने पर ऑपरेशन पूरा होने की गारंटी रहती है
- वही कोड सिंक्रोनस/असिंक्रोनस दोनों environments में बिना बदलाव काम करता है
- लाइब्रेरी लेखक को execution mode के बारे में अलग से सोचना नहीं पड़ता
async() / concurrent() के साथ पैरलल रनिंग
async()फंक्शन असिंक्रोनस रन की request करता है, लेकिनIo.Threadedमें तुरंत रन भी हो सकता हैIo.Eventedमें यह वास्तविक असिंक्रोनस रन बन जाता है और दो files एक साथ save की जा सकती हैं
concurrent()फंक्शन तब इस्तेमाल होता है जब real parallel execution की ज़रूरत होIo.Threadedमें thread pool इस्तेमाल होता हैIo.Eventedमें यहasync()की तरह ही हेंडल होता है
- गलत फंक्शन चुनना (
asyncकी जगहconcurrent) bug माना जाता है और भाषा स्तर पर इसे prevent नहीं किया जा सकता
कोड शैली और भाषा एकीकरण
- बिना किसी async-only सिंटैक्स के सामान्य Zig कोड शैली बरकरार रहती है
try,deferजैसी मौजूदा control-flow syntax वैसे की वैसे ही रहती है- Andrew Kelley ने कहा, “यह standard Zig code की तरह पढ़ता है”
- उदाहरण के रूप में असिंक्रोनस DNS lookup implementation दिखाया गया
getaddrinfo()के विपरीत यह केवल पहला successful response लौटाता है और बाकी requests cancel कर देता है
आगे की योजना और वर्तमान स्थिति
Io.Eventedअभी भी experimental phase में है और कुछ OS पर उपलब्ध नहीं है- WebAssembly-compatible
Ioimplementation की planning है, और इससे जुड़ा फीचर development अभी बाकी है Ioसे संबंधित 24 follow-up tasks मौजूद हैं और अधिकांश अभी अधूरे हैं- Zig अभी 1.0 से पहले के चरण में है; मुख्य बचे हुए मुद्दों में async I/O और native code generation शामिल हैं
- इस डिजाइन से अपेक्षा है कि
I/Ointerface बदलने पर होने वाले code rewrite की frequency कम होगी
समुदाय चर्चा का सार
- कई टिप्पणियों में Zig का approach Rust के async/await मॉडल से सरल और अधिक flexible बताया गया
- Rust में कई executor मिलाकर इस्तेमाल करने पर complexity बढ़ती है
- Zig का
Iointerface एकाधिक executor के coexist करने की संभावना देता है
- कुछ लोगों ने कहा कि कोड थोड़ा verbose हो सकता है
- लेकिन स्पष्ट API design से security, performance और testability पर बेहतर control मिलता है
- async execution और thread execution के अंतर, तथा stackful vs stackless coroutine implementation जैसे तकनीकी मुद्दों पर चर्चा जारी रही
- Zig का
Ioबिना किसी language-level special handling के standard library extension के रूप में implement किया गया है- भविष्य में stackless coroutine फीचर जोड़ा जाना है
निष्कर्ष
- Zig का नया async मॉडल language simplicity और high-performance I/O को साथ लेकर चलने का लक्ष्य रखता है
- function coloring समस्या का समाधान, सिंक्रोनस-असिंक्रोनस कोड का एकीकरण, और explicit control structure के जरिए इसे Zig 1.0 stabilization का एक महत्वपूर्ण चरण माना जा रहा है
1 टिप्पणियां
Hacker News की राय
कुल मिलाकर यह लेख सटीक है और अच्छी तरह से रिसर्च किया गया है
बस कुछ छोटे सुधार हैं।
Io.Threadedinstance मेंasync()वास्तव में asynchronous तरीके से काम नहीं करता, बल्कि तुरंत चल जाता है। लेकिनstd.Io.Threadedडिफ़ॉल्ट रूप से thread pool का उपयोग करके async काम बाँटता है।हालांकि, अगर इसे
init_single_threadedसे initialize किया जाए, तो यह लेख में बताए गए व्यवहार की तरह काम करता है।और एक बात, पहले
asyncConcurrent()नाम का एक function था, लेकिन अब उसका नाम बदलकर सिर्फconcurrent()कर दिया गया हैआगे feedback देना हो तो lwn@lwn.net पर mail भेज सकते हैं।
सुधार सुझावों और Zig पर किए गए काम के लिए धन्यवाद
अगर कोई
async()की जगह गलती सेasyncConcurrent()इस्तेमाल कर दे, तो किस तरह का bug पैदा होगा, यह जानना चाहता हूँ।क्या IO model के हिसाब से यह UB(undefined behavior) भी बन सकता है, या फिर यह सिर्फ एक logic error होगा?
concurrent()की अच्छी बात यह है कि यह code की readability और expressiveness बढ़ाता है, और साफ दिखाता है कि “यह code ज़रूर parallel में चलना चाहिए”मुझे यह design काफ़ी reasonable लगता है।
लेकिन Zig की व्याख्या उलझाने वाली है।
वे ज़ोर देते हैं कि उन्होंने function coloring समस्या हल कर दी है, लेकिन असल में उन्होंने सिर्फ IO को effect type के रूप में आगे बढ़ाया है।
इसमें caller को token बनाए रखना पड़ता है, इसलिए यह अब भी एक तरह की coloring ही है।
यह Go के asynchronous handling approach जैसा लगता है
Zig का पुराना async-await model भी coloring समस्या पहले ही हल कर चुका था।
क्योंकि compiler call context के हिसाब से synchronous/asynchronous versions अपने-आप generate कर देता था
Zig इसे dependency injection से हल करता है, और व्यवहार में यह काफ़ी है।
async call की जटिलता से बचा नहीं जा सकता, लेकिन precise control के लिए यह मानना पड़ेगा
आप global io variable declare करके उसे कहीं भी इस्तेमाल कर सकते हैं (हालाँकि library लिखते समय यह recommended नहीं है)।
function coloring की पाँच शर्तों को समेटने वाला What color is your function? लेख देखें, तो Zig का approach शायद कुछ शर्तें, खासकर 4 और 5, पूरी नहीं करता
लेकिन ऐसा approach deadlock जैसी समस्याएँ पैदा कर सकता है।
कुछ code thread-safe नहीं होता, इसलिए coloring कभी-कभी मददगार भी होती है
यह design Scala के async से बहुत मिलता-जुलता लगता है।
Scala में execution context implicit parameter के रूप में पास होता है, जबकि Zig इसे explicitly लेता है।
व्यवहार में यह सीधे threads और queues इस्तेमाल करने से बहुत बेहतर नहीं था, और execution context management ने जटिल और अप्रत्याशित व्यवहार पैदा किया।
लगता है Zig टीम को Scala का अनुभव कम है, इसलिए उन्हें यह approach नया लगा
JVM इसे virtual threads से संभालता है, लेकिन low-level languages के लिए वही efficiency पाना मुश्किल है।
इसलिए Zig जैसी languages को scalability के लिए दूसरा रास्ता चाहिए
Zig के पुराने async/await system में function का suspend/resume संभव था।
इस feature से मैं OS development में device interrupt आधारित frame suspend/resume लागू करके देखना चाहता था।
नए io system में लगता है यह मुझे खुद implement करना पड़ेगा, जो थोड़ा अफ़सोसजनक है
@asyncSuspendऔर@asyncResumeनाम के low-level builtins मौजूद हैं।नया Io synchronous, threaded, और event-based व्यवहार का common abstraction है, इसलिए इसमें suspend mechanism शामिल नहीं है
मौजूदा Io.Evented prototype देखें, तो यह stackless coroutine के आधार पर 3rd-party libraries में भी संभाला जा सकता है
example code में कहा गया था कि
writeAll()return होते ही काम पूरा हो जाता है,लेकिन IO implementation अलग-अलग हो सकती है, इसलिए वास्तव में completion की guarantee defer शुरू होने के समय तक होनी चाहिए।
नहीं तो
createFileऔरwriteAllके बीच dependency tracking की ज़रूरत पड़ेगी।उस स्थिति में यह आखिरकार blocking call से अलग नहीं लगेगा।
और इस interface का नाम IO क्यों है, यह भी स्पष्ट नहीं है।
असल में यह “किसी दूसरे context में execute करना” जैसी abstraction के ज़्यादा करीब है
संबंधित दस्तावेज़: std.Io
अगला example दिलचस्प है
Rust या Python में coroutine await न किए जाने तक आगे नहीं बढ़ती।
इसके विपरीत, अगर Zig के example में
io.asyncअपने-आप आगे बढ़ता है, तो यह task spawning जैसा होगा।यह एक valid design है, लेकिन दूसरे languages ने आमतौर पर यह रास्ता नहीं चुना
asyncfunction yield होने तक calling thread पर चलता है.await(io)call करना execution guarantee के लिए ज़रूरी है।यह तुरंत चलेगा या thread pool में queue होगा, यह Io runtime implementation पर निर्भर करता है
awaitके समय आगे बढ़ती है।evented io में दोनों काम interleaved तरीके से चल सकते हैं, और threaded io में background में चल सकते हैं।
यानी “कहीं छिपकर अपने-आप चलने वाले tasks” जैसी कोई चीज़ नहीं है
मैं रोज़ Go इस्तेमाल करता हूँ, और उस नज़रिए से Zig का Io मुझे Go की कई कमियों को सुधारता हुआ लगता है।
लेकिन यह जानना चाहूँगा कि क्या Zig में channel जैसा concept है।
Go में
selectkeyword है, लेकिन उसे sockets पर इस्तेमाल न कर पाना हमेशा खलता रहा हैGo के channels में दर्जनों cycles का overhead होता है, इसलिए छोटे-granularity वाले IO के लिए यह अक्षम है।
इसके बजाय बड़े data transfers या many-to-many synchronization में यह उपयोगी है
std.Io.Queueमौजूद है।selectजैसी चीज़ भी लागू की जा सकती है, लेकिन syntax के लिहाज़ से यह कम ergonomic है।बदले में इसका फायदा यह है कि यह GC के बिना अलग-अलग IO runtimes पर काम कर सकता है
Zig का “colorless” approach मुझे कहीं बेहतर लगता है
Goroutines बस green threads हैं, channels सिर्फ thread-safe queues हैं, और Zig यह सब standard library में पहले से देता है
Zig का async version Io, Go के approach से लगभग एक जैसा दिखता है।
लेकिन Go में C library calls के साथ stack allocation cost ज़्यादा होती है, और direct syscalls में platform compatibility की समस्या आती है।
लगता है Zig ने इसे configurable बना दिया है, ताकि code बदले बिना अलग-अलग trade-offs चुने जा सकें
नया async IO simple examples में शानदार है, लेकिन server-level complex IO में इसकी सीमाएँ हो सकती हैं।
संबंधित issue GitHub पर दर्ज है
मूल समस्या यह है कि language या library designers को अलग-अलग execution contexts (sync/async) के बीच पुल बनाने का तरीका देना चाहिए।
इसके लिए context को FSM(finite state machine) में wrap करना और दोनों पक्षों के बीच communication channel देना ज़रूरी है
संबंधित लेख: Function colors represent different execution contexts