4 पॉइंट द्वारा GN⁺ 2025-12-04 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • 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 से हैंडल करने में सक्षम है
  • नया डिजाइन Io generic interface के आसपास काम करता है
    • सभी I/O फंक्शन Io instance को parameter के रूप में लेकर रन होते हैं
    • Allocator इंटरफेस जैसा ढांचा, यानी memory allocation की तरह I/O control करना संभव है

Io इंटरफेस की संरचना

  • स्टैंडर्ड लाइब्रेरी में दो बुनियादी implementations शामिल हैं
    • Io.Threaded: डिफ़ॉल्ट रूप से सिंक्रोनस रन, ज़रूरत पड़ने पर thread-based parallel handling
    • Io.Evented: इवेंट-लूप आधारित असिंक्रोनस रन (io_uring, kqueue आदि का उपयोग)
  • यूज़र्स सीधे नया Io implementation लिख सकते हैं, जिससे 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 Io implementation की planning है, और इससे जुड़ा फीचर development अभी बाकी है
  • Io से संबंधित 24 follow-up tasks मौजूद हैं और अधिकांश अभी अधूरे हैं
  • Zig अभी 1.0 से पहले के चरण में है; मुख्य बचे हुए मुद्दों में async I/O और native code generation शामिल हैं
  • इस डिजाइन से अपेक्षा है कि I/O interface बदलने पर होने वाले code rewrite की frequency कम होगी

समुदाय चर्चा का सार

  • कई टिप्पणियों में Zig का approach Rust के async/await मॉडल से सरल और अधिक flexible बताया गया
    • Rust में कई executor मिलाकर इस्तेमाल करने पर complexity बढ़ती है
    • Zig का Io interface एकाधिक 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 टिप्पणियां

 
GN⁺ 2025-12-04
Hacker News की राय
  • कुल मिलाकर यह लेख सटीक है और अच्छी तरह से रिसर्च किया गया है
    बस कुछ छोटे सुधार हैं।
    Io.Threaded instance में async() वास्तव में asynchronous तरीके से काम नहीं करता, बल्कि तुरंत चल जाता है। लेकिन std.Io.Threaded डिफ़ॉल्ट रूप से thread pool का उपयोग करके async काम बाँटता है।
    हालांकि, अगर इसे init_single_threaded से initialize किया जाए, तो यह लेख में बताए गए व्यवहार की तरह काम करता है।
    और एक बात, पहले asyncConcurrent() नाम का एक function था, लेकिन अब उसका नाम बदलकर सिर्फ concurrent() कर दिया गया है

    • मैं Daroc हूँ। इस feedback को ध्यान में रखते हुए मैंने लेख में दो सुधार लागू किए हैं।
      आगे feedback देना हो तो lwn@lwn.net पर mail भेज सकते हैं।
      सुधार सुझावों और Zig पर किए गए काम के लिए धन्यवाद
    • Andrew से एक सवाल है।
      अगर कोई 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 जैसा लगता है

    • अगर सिर्फ अलग arguments के साथ call करने से ही कोई ‘colored function’ बन जाए, तो फिर हर function colored हो जाएगा और बात का मतलब ही खत्म हो जाएगा ;)
      Zig का पुराना async-await model भी coloring समस्या पहले ही हल कर चुका था।
      क्योंकि compiler call context के हिसाब से synchronous/asynchronous versions अपने-आप generate कर देता था
    • वास्तव में function coloring की मुख्य समस्या synchronous/asynchronous code paths की duplication है।
      Zig इसे dependency injection से हल करता है, और व्यवहार में यह काफ़ी है।
      async call की जटिलता से बचा नहीं जा सकता, लेकिन precise control के लिए यह मानना पड़ेगा
    • Zig का io कोई contagious effect type नहीं है।
      आप global io variable declare करके उसे कहीं भी इस्तेमाल कर सकते हैं (हालाँकि library लिखते समय यह recommended नहीं है)।
      function coloring की पाँच शर्तों को समेटने वाला What color is your function? लेख देखें, तो Zig का approach शायद कुछ शर्तें, खासकर 4 और 5, पूरी नहीं करता
    • असल में Zig शायद हर चीज़ को async से रंग देता है, और सिर्फ यह चुनने देता है कि worker threads इस्तेमाल करने हैं या नहीं।
      लेकिन ऐसा approach deadlock जैसी समस्याएँ पैदा कर सकता है।
      कुछ code thread-safe नहीं होता, इसलिए coloring कभी-कभी मददगार भी होती है
    • Haskell developer के नज़रिए से देखें तो Zig ऐसा लगता है जैसे उसने language support के बिना IO monad लागू कर दिया हो
  • यह design Scala के async से बहुत मिलता-जुलता लगता है।
    Scala में execution context implicit parameter के रूप में पास होता है, जबकि Zig इसे explicitly लेता है।
    व्यवहार में यह सीधे threads और queues इस्तेमाल करने से बहुत बेहतर नहीं था, और execution context management ने जटिल और अप्रत्याशित व्यवहार पैदा किया।
    लगता है Zig टीम को Scala का अनुभव कम है, इसलिए उन्हें यह approach नया लगा

    • अगर सीधे OS threads इस्तेमाल करें, तो Little's Law के अनुसार scalability की सीमा जल्दी सामने आती है।
      JVM इसे virtual threads से संभालता है, लेकिन low-level languages के लिए वही efficiency पाना मुश्किल है।
      इसलिए Zig जैसी languages को scalability के लिए दूसरा रास्ता चाहिए
    • संदर्भ के लिए Scala का ExecutionContext API देखें, इससे जुड़े concepts बेहतर समझ आते हैं
  • 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 शामिल नहीं है
    • अंततः संभव है कि suspend/resume को user-space standard library functions के रूप में लागू किया जाए।
      मौजूदा Io.Evented prototype देखें, तो यह stackless coroutine के आधार पर 3rd-party libraries में भी संभाला जा सकता है
    • यह भी जानना रोचक होगा कि क्या सिर्फ एक thread pool रखकर suspend/resume लागू किया जा सकता है
    • यह भी सवाल है कि cooperative coroutine को preemptive async के रूप में लागू करने का मतलब क्या होगा
  • example code में कहा गया था कि writeAll() return होते ही काम पूरा हो जाता है,
    लेकिन IO implementation अलग-अलग हो सकती है, इसलिए वास्तव में completion की guarantee defer शुरू होने के समय तक होनी चाहिए।
    नहीं तो createFile और writeAll के बीच dependency tracking की ज़रूरत पड़ेगी।
    उस स्थिति में यह आखिरकार blocking call से अलग नहीं लगेगा।
    और इस interface का नाम IO क्यों है, यह भी स्पष्ट नहीं है।
    असल में यह “किसी दूसरे context में execute करना” जैसी abstraction के ज़्यादा करीब है
    संबंधित दस्तावेज़: std.Io

  • अगला example दिलचस्प है

    var a_future = io.async(saveFile, .{io, data, "saveA.txt"});
    var b_future = io.async(saveFile, .{io, data, "saveB.txt"});
    const a_result = a_future.await(io);
    const b_result = b_future.await(io);
    

    Rust या Python में coroutine await न किए जाने तक आगे नहीं बढ़ती।
    इसके विपरीत, अगर Zig के example में io.async अपने-आप आगे बढ़ता है, तो यह task spawning जैसा होगा।
    यह एक valid design है, लेकिन दूसरे languages ने आमतौर पर यह रास्ता नहीं चुना

    • C# भी काफ़ी हद तक ऐसा ही करता है। async function yield होने तक calling thread पर चलता है
    • Zig में भी इसी तरह .await(io) call करना execution guarantee के लिए ज़रूरी है।
      यह तुरंत चलेगा या thread pool में queue होगा, यह Io runtime implementation पर निर्भर करता है
    • वास्तव में execution await के समय आगे बढ़ती है।
      evented io में दोनों काम interleaved तरीके से चल सकते हैं, और threaded io में background में चल सकते हैं।
      यानी “कहीं छिपकर अपने-आप चलने वाले tasks” जैसी कोई चीज़ नहीं है
    • JavaScript भी इसी तरह काम करता है
  • मैं रोज़ Go इस्तेमाल करता हूँ, और उस नज़रिए से Zig का Io मुझे Go की कई कमियों को सुधारता हुआ लगता है।
    लेकिन यह जानना चाहूँगा कि क्या Zig में channel जैसा concept है।
    Go में select keyword है, लेकिन उसे sockets पर इस्तेमाल न कर पाना हमेशा खलता रहा है

    • यह बताया गया कि हर IO को channels में लपेटना महँगा पड़ता है
      Go के channels में दर्जनों cycles का overhead होता है, इसलिए छोटे-granularity वाले IO के लिए यह अक्षम है।
      इसके बजाय बड़े data transfers या many-to-many synchronization में यह उपयोगी है
    • Zig में Go के channels जैसा std.Io.Queue मौजूद है।
      select जैसी चीज़ भी लागू की जा सकती है, लेकिन syntax के लिहाज़ से यह कम ergonomic है।
      बदले में इसका फायदा यह है कि यह GC के बिना अलग-अलग IO runtimes पर काम कर सकता है
    • क्या आपने Odin language इस्तेमाल की है? यह Zig की तुलना में Go से ज़्यादा प्रेरित “better C” है
    • C# के async/await की तरह colored functions को मजबूर न करना मुझे पसंद है।
      Zig का “colorless” approach मुझे कहीं बेहतर लगता है
    • यह मान लेना कि Go का concurrency model कुछ बहुत विशेष है, अपने-आप में समस्या है।
      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