1. समक्रम प्रस्तुतियों की भूमिका

पारस्परिक प्रोग्रामिंग में, जब कई गोरूटीन संसाधन साझा करती हैं, तो संसाधन का प्रवेश केवल एक गोरूटीन द्वारा ही कर गोरूटीन द्वारा रेस शर्तों को निवारण करने के लिए। इसका मतलब समक्रम साधनों का उपयोग करना होता है। समक्रम साधन समक्रम पर्यावरण में डेटा संगतता और स्थिति समक्रमण सुनिश्चित करते हैं।

Go भाषा प्रकार समक्रमण साधनों का एक समृद्ध सेट प्रदान करती है, जिसमें निम्नलिखित शामिल हैं (लेकिन सीमित नहीं हैं):

  • म्यूटेक्स (sync.Mutex) और रीड-राइट म्यूटेक्स (sync.RWMutex)
  • चैनल
  • वेटग्रुप्स
  • परमाणु कार्य (परमाणु पैकेज)
  • स्थिति चर वेरिएबल्स (sync.Cond)

2. समक्रमण आधारपुट

2.1 म्यूटेक्स (sync.Mutex)

2.1.1 म्यूटेक्स की अवधारणा और भूमिका

म्यूटेक्स एक समक्रमण साधन है जो सुनिश्चित करता है कि केवल एक गोरूटीन समय के किसी भी समय में जब लॉक को पहुँचने की अनुमति देता है, तो साझा संसाधन का सुरक्षित ऑपरेशन कर सकता है। म्यूटेक्स द्वारा समक्रमण को 'लॉक' और 'अनलॉक' विधियों के माध्यम से प्राप्त किया जाता है। 'लॉक' विधि को ब्लॉक किया जाएगा जब तक ताला रिलीज नहीं होता है, और इस समय, तब अन्य गोरूटीन जो लॉक प्राप्त करने का प्रयास कर रहे हैं, वे इंतजार करेंगे। 'अनलॉक' कोल करने से लॉक रिलीज होता है, जो अन्य इंतजार कर रहे गोरूटीन्स को उसे प्राप्त करने देता है।

var mu sync.Mutex

func criticalSection() {
    // सम्भावना ताला से एक्सक्लूसिव रूप से संसाधन तक पहुंच
    mu.Lock()
    // यहां साझाकून संसाधन तक पहुंचें
    // ...
    // अनलॉक करने से अन्य गोरूटीन संसाधन प्राप्त करने के लिए
    mu.Unlock()
}

2.1.2 म्यूटेक्स का व्यावहारिक उपयोग

मान लीजिए हमें एक ग्लोबल काउंटर बनाए रखना है, और कई गोरूटीन्स को इसके मूल्य को बढ़ाने की आवश्यकता है। म्यूटेक्स का उपयोग करके हम काउंटर की सटीकता को सुनिश्चित कर सकते हैं।

var (
    mu      sync.Mutex
    counter int
)

func increment() {
    mu.Lock()         // मूल्य में परिवर्तन करने से पहले लॉक करें
    counter++         // सुरक्षित रूप से काउंटर को बढ़ाएं
    mu.Unlock()       // ऑपरेशन के बाद अनलॉक करें, जिससे अन्य गोरूटीन्स को काउंटर तक पहुँच मिल सके
}

func main() {
    for i := 0; i < 10; i++ {
        go increment()  // कॉउंटर के मान को बढ़ाने के लिए कई गोरूटीन्स शुरू करें
    }
    // कुछ समय के लिए इंतजार करें (अभ्यास में, आपको वेटग्रुप या अन्य तरीकों का उपयोग करना चाहिए जिससे सभी गोरूटीन्स को पूरा होने का इंतजार करना चाहिए)
    time.Sleep(1 * time.Second)
    fmt.Println(counter)  // कॉउंटर के मान का मूल्य निकालें
}

2.3.1 शर्ती चरवारियों की अवधारणा

Go भाषा के समक्रमण यांत्रिकीकरण में, शर्ती चरवारियों का उपयोग सिंक्रनाइज़ेशन प्राथमिकता के रूप में किया जाता है। शर्ती चरवारियों का हमेशा sync.Mutex के साथ उपयोग किया जाता है, जो शर्ती की संरचना की संरचना की समता को सुरक्षित रखने के लिए प्रयोग किया जाता है।

शर्ती चरवारियों की अवधारणा ऑपरेटिंग सिस्टम डोमेन से आती है, जो एक समूह गोरूटीन्स को किसी निश्चित शर्त को पूरा होने के लिए प्रतीक्षा करने की अनुमति देती है। अधिक विशेष रूप से, एक गोरूटीन निश्चित शर्त पूरा होने की प्रतीक्षा करते समय क्रियान्वयन को रोक सकती है, और एक और गोरूटीन शर्ती चरवारीयों का प्रयोग करके शर्त को बदलने के बाद अन्य गोरूटीन को पुनरारंभ करने के लिए सूचित कर सकती है।

गो मानक पुस्तकालय में, शर्ती चरवारियाँ sync.Cond प्रकार के माध्यम से प्रदान की जाती हैं, और उसकी मुख्य विधियों में शामिल हैं:

  • Wait: इस विधि को बुलाने से होल्ड की गई ताला खोल दिया जाएगा और ब्लॉक हो जाएगा जब तक किसी और गोरूटीन ने इसी शर्ती चरवारीय को जगाने के लिए Signal या Broadcast को कॉल करके इसे जागृत कर दे, उसके बाद वह फिर से ताला हासिल करने का प्रयास करेगा।
  • Signal: इस शर्ती चरवारीय के लिए इंतजार कर रहें एक गोरूटीन को जगा देता है। यदि कोई गोरूटीन इंतजार कर रहा नहीं है, तो इस विधि को बुलाने का कोई प्रभाव नहीं होगा।
  • Broadcast: इस शर्ती चरवारीय के लिए इंतजार कर रहें सभी गोरूटीन्स को जगा देता है।

शर्ती चरवारियों को कॉपी नहीं किया जाना चाहिए, इसलिए वे सामान्यत: किसी विशिस्ट संरचना के प्वाइंटर फील्ड के रूप में प्रयोग किए जाते हैं।

2.4.2 WaitGroup का प्रैक्टिकल उपयोग

इस उदाहरण में, WaitGroup का उपयोग निम्नलिखित अनुसरण द्वारा किया गया है:

package main

import (
	"fmt"
	"sync"
	"time"
)

func worker(id int, wg *sync.WaitGroup) {
	defer wg.Done() // पूर्ण होने पर WaitGroup को सूचित करें

	fmt.Printf("Worker %d आरंभ कर रहा है\n", id)
	time.Sleep(time.Second) // समय ग्राहक आघात को सिमुलेट करें
	fmt.Printf("Worker %d समाप्त\n", id)
}

func main() {
	var wg sync.WaitGroup

	for i := 1; i <= 5; i++ {
		wg.Add(1) // Goroutine आरंभ होने से पहले काउंटर बढ़ाएँ
		go worker(i, &wg)
	}

	wg.Wait() // सभी कर्मचारी गोरूटीन के पूर्ण हो जाने का इंतजार करें
	fmt.Println("सभी कर्मचारी सम्पन्न हो गए")
}

इस उदाहरण में, worker फ़ंक्शन कोई कार्य का प्रस्तुतीकरण करने का अनुकरण करता है। मुख्य फ़ंक्शन में, हम पाँच worker गोरूटीन को प्रारंभ करते हैं। हर गोरूटीन को प्रारंभ करने से पहले, हम wg.Add(1) को कॉल करते हैं ताकि WaitGroup को सूचित किया जाए कि एक नया कार्य प्रस्तुत हो रहा है। जब प्रत्येक कार्यकर्ता फ़ंक्शन पूर्ण होता है, तो वह defer wg.Done() को कॉल करता है ताकि WaitGroup को सूचित किया जाए कि कार्य समाप्त है। सभी गोरूटीन्स को प्रारंभ करने के बाद, मुख्य फ़ंक्शन wg.Wait() पर ब्लॉक हो जाता है जब तक सभी कर्मचारी पूर्णता रिपोर्ट नहीं करते।

2.5 एटॉमिक ऑपरेशंस (sync/atomic)

2.5.1 एटॉमिक ऑपरेशन की अवधारणा

एटॉमिक ऑपरेशन संवर्धित प्रोग्रामिंग में ऐसी ऑपरेशन्स को संदर्भित करती है जो एकत्रित नहीं होती हैं, यानी इनका कोई बीच की ऑपरेशंस द्वारा अवरुद्ध नहीं किया जा सकता। एकाधिक गोरूटीन्स के लिए, एटॉमिक ऑपरेशन का उपयोग डेटा संरचना और स्थिति समक्रमण की संरचना बिना लॉकिंग की जरूरत के सुनिश्चित कर सकते हैं, क्योंकि एटॉमिक ऑपरेशनस खुद व्युत्पंन अवधता की गारंटी देती हैं।

गो भाषा में, sync/atomic पैकेज लो-स्तरीय एटॉमिक स्मृति ऑपरेशन प्रदान करता है। int32, int64, uint32, uint64, uintptr, और pointer जैसे मूल डेटा प्रकारों के लिए, sync/atomic पैकेज के विधियों का उपयोग सुरक्षित समक्रमिक ऑपरेशन्स के लिए किया जा सकता है। एटॉमिक ऑपरेशन का महत्व इस बात में होता है कि वे अन्य समक्रमण पूरक (जैसे ताले और स्थिति सूचक) के निर्माण के लिए प्रमुख विचारक होते हैं और अक्सर ताले संचालन विधियों से अधिक प्रभावी होते हैं।

2.5.2 एटॉमिक ऑपरेशन के प्रैक्टिकल उपयोग

सोचें एक परिदृश्य को जहां हमें वेबसाइट पर एक साइट पर दर्शकों की संभवता नंबर को ट्रैक करने की आवश्यकता होती है। साधारण काउंटर चर जीवंतता में हम दर्शक आते ही काउंटर को बढ़ा देंगे और दर्शक चले जाते हैं तो घटा देंगे। हालांकि, संकल्पना के तिव्रता से युक्त परिदृश्य में, यह विचार डेटा रेस की ओर बढ़ायेगा। इसलिए, हम sync/atomic पैकेज का उपयोग करके सुरक्षित रूप से काउंटर को प्रगणन कर सकते हैं।

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"
)

var visitorCount int32

func incrementVisitorCount() {
	atomic.AddInt32(&visitorCount, 1)
}

func decrementVisitorCount() {
	atomic.AddInt32(&visitorCount, -1)
}

func main() {
	var wg sync.WaitGroup
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func() {
			incrementVisitorCount()
			time.Sleep(time.Second) // चालक के दौरे का समय
			decrementVisitorCount()
			wg.Done()
		}()
	}
	wg.Wait()
	fmt.Printf("वर्तमान दर्शक गिनती: %d\n", visitorCount)
}

इस उदाहरण में, हम 100 गोरूटीन्स बनाते हैं जो दर्शकों के आगमन और विचलन का अनुकरण करते हैं। atomic.AddInt32() फ़ंक्शन का उपयोग करके, हम सुनिश्चित करते हैं कि काउंटर की वृद्धि और घटना एकत्रित परिदृश्यों में भी अणुवर्ती है, ऐसी स्थिति में भी, जो दर्शाता है कि visitorCount की सटिकता को सुनिश्चित करता है।

2.6 चैनल सिंक्रोनाइजेशन तंत्र

2.6.1 चैनल की सिंक्रोनाइजेशन विशेषताएँ

चैनल गो भाषा में गोरूटीन के साथ संवाद करने का एक तरीका है। एक चैनल डेटा भेजने और प्राप्त करने की क्षमता प्रदान करता है। जब एक गोरूटीन चैनल से डेटा पढ़ने का प्रयास करता है और चैनल के पास कोई डेटा नहीं होता है, तो यह इस समय तक ब्लॉक हो जाता है जब तक डेटा उपलब्ध नहीं होता है। उसी तरह, यदि चैनल भरा होता है (एक बफर रहित चैनल के लिए, यह अर्थ होता है कि उसमें पहले से ही डेटा है), तो डेटा भेजने का प्रयास करने वाला गोरूटीन भी इस समय तक ब्लॉक हो जाता है जब तक लिखने के लिए स्थान नहीं होता है। यह विशेषता चैनल को गोरूटीन्स के बीच समक्रमण के लिए बहुत उपयोगी बनाती है।

2.6.2 चैनल के साथ समक्रमण के उपयोग के मामले

मान लें कि हमारे पास एक कार्य है जिसे कई गोरूटीन्स द्वारा पूरा करना है, प्रत्येक एक उपकार का संभालन करता है, और फिर हमें सभी उपकारों के परिणामों को एकत्रित करना है। हम एक चैनल का उपयोग करके सभी गोरूटीनों को समाप्त होने का इंतजार करने के लिए कर सकते हैं।

package main

import (
    "fmt"
    "sync"
)

func worker(id int, wg *sync.WaitGroup, resultChan chan<- int) {
    defer wg.Done()
    // कुछ कार्रवाई करें...
    fmt.Printf("कर्मी %d शुरू हो रहा है\n", id)
    // मान लें कि उपकार परिणाम कार्मी का आईडी है
    resultChan <- id
    fmt.Printf("कर्मी %d सम्पन्न हो गया है\n", id)
}

func main() {
    var wg sync.WaitGroup
    numWorkers := 5
    resultChan := make(chan int, numWorkers)

    for i := 0; i < numWorkers; i++ {
        wg.Add(1)
        go worker(i, &wg, resultChan)
    }

    go func() {
        wg.Wait()
        close(resultChan)
    }()

    // सभी परिणामों को एकत्र करें
    for result := range resultChan {
        fmt.Printf("परिणाम प्राप्त किया: %d\n", result)
    }
}

इस उदाहरण में, हम 5 गोरूटीनों को कार्य संपादित करने के लिए प्रारंभ करते हैं और उनके परिणामों को resultChan चैनल के माध्यम से एकत्रित करते हैं। मुख्य गोरूटीन एक अलग गोरूटीन में सभी काम समाप्त होने का इंतजार करता है और फिर resultChan चैनल को बंद करता है। इसके बाद, मुख्य गोरूटीन resultChan चैनल को दौरा करता है, सभी गोरूटीनों के परिणामों को एकत्रित करता है और प्रिंट करता है।

2.7 एक-बार का निष्पादन (sync.Once)

sync.Once एक समक्रमण मौखिक है जो प्रोग्राम के निष्पादन के दौरान केवल एक बार कार्रवाई होने का सुनिश्चित करता है। sync.Once का एक सामान्य उपयोग एक सिंगलटन ऑब्जेक्ट की प्रारंभिकीकरण में या विलंबित प्रारंभण की आवश्यकता वाली स्थितियों में होता है। इस कार्रवाई को जितने भी गोरूटीन्स बुलाएं, वह केवल एक बार चलेगी, इसलिए Do फ़ंक्शन का नाम है।

sync.Once एक समक्रमण मौखिक द्वारा पूर्णता से परस्पर संबंधित समस्याओं और निष्पादन क्षमता का संतुलन आपत्तियों को मिटाता है, जो दोहरी प्रारंभण से होने वाली प्रदर्शन समस्याओं के बारे में चिंताओं को हटा देता है।

sync.Once के उपयोग का एक सरल उदाहरण देखने के लिए:

package main

import (
    "fmt"
    "sync"
)

var once sync.Once
var instance *Singleton

type Singleton struct{}

func Instance() *Singleton {
    once.Do(func() {
        fmt.Println("अब एकल उदाहरण बनाया जा रहा है।")
        instance = &Singleton{}
    })
    return instance
}

func main() {
    for i := 0; i < 10; i++ {
        go Instance()
    }
    fmt.Scanln() // आउटपुट देखने के लिए प्रतीक्षा करें
}

इस उदाहरण में, यदि Instance फ़ंक्शन को समक्रमित रूप से कई बार बुलाया जाता है, तो Singleton इंस्टेंस का निर्माण केवल एक बार होगा। आगामी बुलाव यथार्थता में पहली बार बनाए गए एकल इंस्टेंस को सीधे वापस करेंगे, इंस्टेंस की अनन्यता सुनिश्चित करते हुए।

2.8 ErrGroup

ErrGroup गो भाषा में एक पुस्तकालय है जिसका उपयोग किया जाता है गैर-समक्रमण एवं उनकी त्रुटियों को समक्रमित करने के लिए। यह "golang.org/x/sync/errgroup" पैकेज का हिस्सा है, समक्रमण संचालन में त्रुटि स्थितियों को संभालने का एक संक्षेपित तरीका प्रदान करता है।

2.8.1 ErrGroup की अवधारणा

ErrGroup की मूल विचारणा एक समूह रिष्तित कार्यों (सामान्यत: समक्रमित रूप से निष्पादित) को बाँधना है, और यदि इन कार्यों में से कोई भी असफल होता है, तो समूह के संपादन को रद्द कर दिया जाएगा। साथ ही, यदि इन समक्रमित ऑपरेशनों में से किसी भी में त्रुटि होती है, तो ErrGroup इस त्रुटि को पकड़ेगा और लौटाएगा।

ErrGroup का उपयोग करने के लिए, सबसे पहले पैकेज को आयात करें:

import "golang.org/x/sync/errgroup"

फिर, ErrGroup की एक उपयोगता बनाएं:

var g errgroup.Group

इसके बाद, आप बंद करने के रूप में बंद करने और Go विधि को बुलाकर बंद करने संबंधित कार्यों को ErrGroup को पास कर सकते हैं:

g.Go(func() error {
    // कुछ निश्चित कार्य करें
    // और सब कुछ चलता गया
    return nil
    // अगर कोई त्रुटि होती है
    // return fmt.Errorf("त्रुटि हो गई")
})

अंत में, Wait विधि को बुलाएं, जो सभी कार्यों को पूरा करने के लिए ब्लॉक करेगी और ब्लॉक करेगी। यदि इन कार्यों में से कोई भी निष्पादित होता है, तो Wait उस त्रुटि को लौटाएगी:

if err := g.Wait(); err != nil {
    // त्रुटि का सामना करें
    log.Fatalf("कार्य निष्पादन त्रुटि: %v", err)
}

2.8.2 ErrGroup का व्यावसायिक मामला

समझें एक स्थिति जहां हमें तीन विभिन्न डेटा स्रोतों से एक साथ डेटा प्राप्त करने की आवश्यकता है, और यदि किसी भी डेटा स्रोत में विफलता होती है, तो हम चाहते हैं कि अन्य डेटा प्राप्ति प्रक्रियाएँ तुरंत रद्द कर दी जाएं। यह कार्य ErrGroup का उपयोग करके आसानी से पूरा किया जा सकता है:

package main

import (
    "fmt"
    "golang.org/x/sync/errgroup"
)

func fetchDataFromSource1() error {
    // स्रोत 1 से डेटा प्राप्त करने का उत्पादन करें
    return nil // या त्रुटि का उत्पादन करने के लिए एक त्रुटि लौटाएं
}

func fetchDataFromSource2() error {
    // स्रोत 2 से डेटा प्राप्त करने का उत्पादन करें
    return nil // या त्रुटि का उत्पादन करने के लिए एक त्रुटि लौटाएं
}

func fetchDataFromSource3() error {
    // स्रोत 3 से डेटा प्राप्त करने का उत्पादन करें
    return nil // या त्रुटि का उत्पादन करने के लिए एक त्रुटि लौटाएं
}

func main() {
    var g errgroup.Group

    g.Go(fetchDataFromSource1)
    g.Go(fetchDataFromSource2)
    g.Go(fetchDataFromSource3)

    // सभी गोरूटीन को पूरा होने का इंतजार करें और उनकी त्रुटियों को संग्रहित करें
    if err := g.Wait(); err != nil {
        fmt.Printf("डेटा प्राप्त करते समय त्रुटि हुई: %v\n", err)
        return
    }

    fmt.Println("सभी डेटा सफलतापूर्वक प्राप्त कर लिए गए हैं!")
}

इस उदाहरण में, fetchDataFromSource1, fetchDataFromSource2, और fetchDataFromSource3 फ़ंक्शन विभिन्न डेटा स्रोतों से डेटा प्राप्त करने का उत्पादन करती हैं। वे g.Go विधि को पारित किए जाते हैं और अलग-अलग Goroutines में क्रियान्वित होते हैं। यदि किसी फ़ंक्शन से कोई त्रुटि लौटाई जाती है, तो g.Wait उसी त्रुटि को तुरंत लौटाएगा, जिससे त्रुटि होने पर उपयुक्त त्रुटि संभालन की अनुमति होती है। यदि सभी फ़ंक्शन सफलतापूर्वक क्रियान्वित होते हैं, तो g.Wait nil लौटाएगा, जिससे इसका इंगित किया जायेगा कि सभी कार्य सफलतापूर्वक पूरे किए गए हैं।

ErrGroup का एक अन्य महत्वपूर्ण विशेषता यह है कि यदि किसी Goroutine में किसी प्रकार की अवस्था आ जाती है, तो यह उस अवस्था को संभालने का प्रयास करेगा और उसे एक त्रुटि के रूप में लौटाएगा। इससे दूसरी साथ संचालित गोरूटीन गरिमाय बंद करने का असंभावित होने से बचाता है। बेशक, यदि आप चाहते हैं कि कार्यों को बाह्य रद्दी संकेतों का प्रतिसार करे, तो आप errgroup के WithContext विधि को context पैकेज के साथ जोड़कर रद्दीय नियामक संदर्भ प्रदान कर सकते हैं।

इस प्रकार, ErrGroup गो की समकालिक प्रोग्रामिंग अभ्यास में बहुत ही व्यावसायिक समक्रिया और त्रुटि संभालन तंत्र बन जाता है।