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
गो की समकालिक प्रोग्रामिंग अभ्यास में बहुत ही व्यावसायिक समक्रिया और त्रुटि संभालन तंत्र बन जाता है।