Vivasoft-logo

[৪.২] চ্যানেল (Channels)

এই পর্যায়ে আমরা Go চ্যানেল নিয়ে কথা বলবো। চ্যানেল সম্পর্কে জানতে হলে আগে আমাদের জানতে হবে চ্যানেল কী, কীভাবে কাজ করে এবং কেনই বা আমরা চ্যানেল ব্যবহার করবো। বিভিন্ন উদাহরণের মাধ্যমে বিষয়গুলো দেখা যাক।

[৪.২.১] চ্যানেল কী ?

Go তে চ্যানেল হল পাইপ লাইনের মতো যাকে ব্যবহার করে এক বা একাধিক গো-রুটিন নিজেদের মধ্যে যোগাযোগ করে। বাস্তবে যদি আমরা পাইপ লাইনের দিকে তাকাই তাহলে দেখতে পাবো পাইপ লাইনের মাধ্যমে পানি এক প্রান্ত থেকে অন্য প্রান্তে প্রবাহিত হচ্ছে, একইভাবে চ্যানেল ব্যবহার করে এক প্রান্ত থেকে ডাটা পাঠানো হয় এবং অন্য প্রান্ত থেকে ডাটা গ্রহণ করা হয়।  Go তে দুই ধরনের চ্যানেল রয়েছে, buffered চ্যানেল এবং unbuffered চ্যানেল। পরবর্তীতে আমরা buffered এবং unbuffered চ্যানেল নিয়ে আলোচনা করবো।

[৪.২.২] কেন চ্যানেল ব্যবহার করবো ?

  1. একাধিক গো-রুটিনের মধ্যে যোগাযোগ সহজ করার জন্য ও  ডাটা ট্রান্সফারের জন্য চ্যানেল ব্যবহার করা হয়।
  2. Go তে চ্যানেলের ব্যবহার অ্যাসিঙ্ক্রোনাস প্রোগ্রামিংকে সহজ করে তোলে।
  3. Go তে চ্যানেলের ব্যবহার একাধিক ফাংশন বা মেথডকে একইসাথে রান করতে সাহায্য করে।

[৪.২.৩]  চ্যানেল ডিক্লেয়ার

প্রত্যেকটি চ্যানেল এর সাথে একটি নির্দিষ্ট টাইপ এসোসিয়েটেড থাকে। একটি চ্যানেলে শুধু একই টাইপের ডাটা ট্রান্সপোর্ট করতে পারে অন্য টাইপের ডাটাকে এলাও করে না। চ্যানেল এ ভ্যালু রিসিভ এবং সেন্ডের জন্য <- অপারেটর ব্যবহার করা হয়।

সিনট্যাক্স :

				
					myChannel := make(chan datatype)
				
			
gochan

যদি চ্যানেলে কোন মান না থাকে তাহলে সেই চ্যানেলকে nil চ্যানেল বলে। nil চ্যানেল কোন কাজে ব্যবহার করা হয় না। তাই চ্যানেলকে  maps এবং স্লাইস এর মত make ব্যবহার করে ডিফাইন করা হয়।

একটি উদাহরণ দেখা যাক :

				
					  package main

   import "fmt"

   func main() { 
       var ch chan int
       if ch == nil {
           fmt.Println("channel ch is nil, Going to define it")
           ch = make(chan int)
           fmt.Printf("Type of a is %T", ch)
       }
   }
   
   Output :
   channel ch is nil, Going to define it 
   Type of ch is chan int



				
			

উপরের উদাহরণে, আমরা একটি nil চ্যানেল ch ডিফাইন করেছি, তারপর চেক করেছি ch nil কিনা। যেহেতু ch একটা nil চ্যানেল তাই if  এর ভিতরে প্রবেশ করেছে “channel ch is nil, Going to define it” প্রিন্ট হয়েছে, তারপর make দিয়ে চ্যানেল ch কে ডিফাইন করা হয়েছে এবং চ্যানেল ch এর টাইপ প্রিন্ট করা হয়েছে। 

 নিম্নের সিনট্যাক্সের মাধ্যমে চ্যানেলে ডাটা সেন্ড এবং রিসিভ হয়ে থাকে : 

				
					

   ch <- data // write or send to channel a
   data := <- ch // read or receive from channel a 
				
			

চ্যানেল এ ডাটা সেন্ড এবং রিসিভ বাই ডিফল্ট ব্লকিং হয়ে থাকে। অর্থাৎ যখন ডাটা সেন্ড করা হয় গো-রুটিন সেটা রিসিভ করার আগ পর্যন্ত চ্যানেল টি ব্লক থাকে। একই ভাবে ডাটা রিসিভ না হওয়া পর্যন্ত ডাটা সেন্ড বন্ধ থাকে অর্থাৎ  চ্যানেল ব্লক থাকে। এ জন্যই বলা হয় চ্যানেল বাই ডিফল্ট ব্লকিং।

এখন কিছু উদাহরণের মাধ্যমে চ্যানেল ব্যবহার করে গো-রুটিন এর কমিউনিকেশন বুঝার চেষ্টা করি। [4.1.2] এর দ্বিতীয় উদাহরণটা এবার আমরা চ্যানেল এর সাহায্যে করবো : 

				
					package main

   import ( 
       "fmt"
   )

   func hello(done chan bool) { 
       fmt.Println("Hello world Goroutine")
       done <- true   // signal that the work is done
   }
   func main() { 
       done := make(chan bool)
       go hello(done)
       <-done   // wait for the Goroutine to finish
       fmt.Println("main function")
   }

   // Output :
   // Hello world Goroutine
   // main function
				
			

উপরের উদাহরণে, done bool নামে চ্যানেল তৈরি করেছি সেটা hello গো-রুটিন এ প্যারামিটার হিসেবে পাস করেছি, পরের লাইনে hello() থেকে পাঠানো ডাটা <-done এর সাহায্যে রিসিভ করতেছি যা done <- এর সাহায্যে পাঠানো হয়েছিল। অর্থাৎ গো-রুটিন ডাটা সেন্ড না করা পর্যন্ত মেইন ফাংশন ব্লক অবস্থায় থাকবে। এতে করে আমাদের আর time.Sleep() লিখে অপেক্ষা করতে বলা লাগেনি এবং মেইন ফাংশন তার এক্সিকিউশন শেষ করে দেয়নি।  

উপরের উদাহরণটি একটু মডিফাই করা যাক : 

				
					  package main

   import (
       "fmt"
       "time"
   )

   func hello(done chan bool) {
       fmt.Println("hello Go routine is Going to sleep")
       time.Sleep(4 * time.Second)
       fmt.Println("hello Go routine awake and Going to write to done")
       done <- true // signal that the work is done
   }
   
   func main() {
       done := make(chan bool)
       fmt.Println("Main Going to call hello Go Goroutine")
       go hello(done)
       <-done // wait for the Goroutine to finish
       fmt.Println("Main received data")
   }

   // Output :
   // Main Going to call hello Go Goroutine
   // hello Go routine is Going to sleep
   // hello Go routine awake and Going to write to done
   // Main received data

				
			

এবার লক্ষ করা যাক, এই প্রোগ্রামে প্রথমে “Main Going to call hello Go Goroutine” প্রিন্ট হয়েছে,তারপর গো-রুটিন স্টার্ট হয়েছে এবং “hello Go routine is Going to sleep” প্রিন্ট হয়েছে। এটি প্রিন্ট হওয়ার পরে, hello গো-রুটিন 4 সেকেন্ডের জন্য Sleep এ যাবে এবং এই সময়ের মধ্যে মেইন গো-রুটিন ব্লক হয়ে যাবে কারণ এটি চ্যানেল থেকে রেসপন্সের জন্য অপেক্ষা করছে। 4 সেকেন্ড পর “hello Go routine awake and Going to write to done” প্রিন্ট হবে এবং রেসপন্স আসবে তখন “Main received data” প্রিন্ট হবে সাথে সাথে মেইন ফাংশনও বন্ধ হয়ে যাবে।

[৪.২.৪] Unbuffered চ্যানেল  কী ?

আমরা আগেই বলেছি চ্যানেল দুই প্রকার Unbuffered এবং Buffered চ্যানেল । এখন আমরা জানব Unbuffered চ্যানেল সম্পর্কে । আসলে চ্যানেল বাই ডিফল্ট Unbuffered থাকে। Unbuffered চ্যানেল এ ভ্যালু সেন্ডিং (chan <- )  এর করেস্পন্ডিং ভ্যালু রিসিভার (<- chan) থাকবে।

				
					  package main

   import (
       "fmt"
       "time"
   )
   // ---send data into a channel---
   func sendData(ch chan string) {
       fmt.Println("Sending a string into channel...")


       ch <- "Hello"
       fmt.Println("String has been retrieved from channel...")
   }

   // ---getting data from the channel---
   func getData(ch chan string) {
       time.Sleep(2 * time.Second)
       fmt.Println("String retrieved from channel:", <-ch)
   }

   func main() {
       ch := make(chan string)
       go sendData(ch)
       go getData(ch)
       fmt.Scanln()
   }

   // Output :
   // Sending a string into channel...
   // String retrieved from channel: Hello
   // String has been retrieved from channel…
				
			
এখানে আমরা main() ফাংশনে make() ফাংশনের সাহায্যে string টাইপের চ্যানেল ch তৈরি করেছি। গো-রুটিন এর সাহায্যে sendData() এবং getData()  ফাংশন কল করেছি।  sendData()  ফাংশন “Sending a string into channel…” প্রিন্ট করেছে একই সময় getData() ফাংশনও কল হয়েছে সে সাথে সাথে time.Sleep() ফাংশন পেয়ে  2 সেকেন্ড ওয়েটিং এ চলে গেছে ।  2 সেকেন্ড পর ভ্যালু রিসিভ করে “String retrieved from channel : Hello”  প্রিন্ট করেছে।

[৪.২.৫] Buffered চ্যানেল  কী ?

Buffered চ্যানেল  এক ধরনের চ্যানেল যার একটি নির্দিষ্ট Buffer থাকবে যেখানে নির্দিষ্ট সংখ্যক ভ্যালু ষ্টোর করে রাখতে পারে করেস্পন্ডিং কোন রিসিভার ছাড়াই। যখন আমাদের একটি Go রুটিনে ডেটা প্রস্তুত করতে হয় এবং অন্য Go রুটিনে ডেটা সরবরাহ করতে হয় তখন আমরা বাফার চ্যানেলগুলি ব্যবহার করতে পারি।  

 

Buffered চ্যানেল সিনট্যাক্স : 

gochan_buffer

উদাহরণ : 

				
					 package main

   import (
       "fmt"
   )

   func main() {
       linkChannel := make(chan string, 5)
       go ping(linkChannel)
       fmt.Println(<-linkChannel)
       fmt.Println(<-linkChannel)
       fmt.Println(<-linkChannel)
   }

  func ping(c chan string) {
       links := []string{"https://www.golinuxcloud.com/", "https://www.tesla.com/", "https://www.google.com/"}
       for _, link := range links {
           c <- link
       }
   }

   // Output :
   // https://www.golinuxcloud.com/
   // https://www.tesla.com/
   // https://www.google.com/

				
			

উপরের উদাহরণে, একটি buffered চ্যানেল linkChannel যা গো-রুটিন ping এর সাথে পাঠানো হয়েছে। ping() ফাংশন থেকে পাঠানো ভ্যালু main() ফাংশন রিসিভ করেছে । 

[ নোট – buffered চ্যানেল প্যানিক এবং এরর রিটার্ন করে যখন রিসিভার(<- link Channel ) এম্পটি থাকে ] 

[৪.২.৬] চ্যানেল এ Select 

Go-তে, সিলেক্ট স্টেটমেন্ট অনেক ক্ষেত্রেই ব্যবহার করা হয়, বিশেষ করে মাল্টিপল গো-রুটিন ব্যবহার করা হলে তখন অনেক ডাটা শেয়ার হয়, তখন এগুলো সিকুয়েন্স অনুসারে পাওয়ার জন্য সিলেক্ট স্টেটমেন্ট ব্যবহার করা হয়। চ্যানেলে মাল্টিপল ডাটা সিকুয়েন্স অনুস্বারে পাওয়ার জন্যও আমরা সিলেক্ট স্টেটমেন্ট ব্যবহার করতে পারি। সিলেক্ট স্টেটমেন্ট অনেকটাই switch স্টেটমেন্টের মতোই যার সাহায্যে মাল্টিপল চ্যানেল এর সাথে কমিউনিকেশন করা যায়।

সিলেক্ট স্টেটমেন্টের সিনট্যাক্স : 

				
					 select {
       case <-ch1:                         // Discard received value
           fmt.Println("Got something")
      
       case x := <-ch2:                    // Assign received value to x
           fmt.Println(x)
      
       case ch3 <- y:                      // Send y to channel
           fmt.Println(y)
      
       default:
           fmt.Println("None of the above")
       }

				
			

উপরের উদাহরণে, চ্যানেল ch1 যদি রিসিভ স্টাটে থাকে তাহলে সে “Got something” প্রিন্ট করবে,

চ্যানেল ch2 ভ্যালু রিসিভ করে x অ্যাসাইন করে তারপর প্রিন্ট করবে, চ্যানেল ch3, y এর ভ্যালু  সেন্ড করবে আর এই কন্ডিশগুলো সত্য না হলে default এ “None of the above” প্রিন্ট হবে।

[৪.২.৭] ক্লোজিং চ্যানেল(Closing Channel )

প্রোগ্রামের যেখানে যে চ্যানেল ব্যবহৃত হয়, সে জায়গা হতেই চ্যানেলটি বন্ধ করা সম্ভব, close() ফাংশন ব্যবহার করে। চ্যানেলটি বন্ধ করা হয়েছে কিনা তা পরীক্ষা করতে চ্যানেল থেকে ডাটা গ্রহণ করার সময় রিসিভার একটি অতিরিক্ত ভারিয়াবল চেকার হিসেবে ব্যবহার করতে পারে। যেমন – 

				
					v, ok := <- ch
				
			

যদি ok স্টেটমেন্ট true হয় তাহলে রিসিভার বুঝবে এটা সঠিক সেন্ডার থেকে ডাটা এসেছে আর ok স্টেটমেন্ট false হলে রিসিভার বুঝবে এটা চ্যানেল বন্ধ করা হয়েছে। আর একটা বন্ধ চ্যানেল থেকে সর্বদাই 0 অথবা nil মান আসবে।

 

নিচের উদাহরণটি দেখা যাক –

				
					 package main

   import ( 
       "fmt"
   )

   func producer(chnl chan int) { 
       for i := 0; i < 10; i++ {
           chnl <- i
       }
       close(chnl)
   }
   
   func main() { 
       ch := make(chan int)
       go producer(ch)
       for {
           v, ok := <-ch
           if ok == false {
               break
           }
           fmt.Println("Received ", v, ok)
       }
   }
   // Output:
   // Received  0 true 
   // Received  1 true 
   // Received  2 true 
   // Received  3 true 
   // Received  4 true 
   // Received  5 true 
   // Received  6 true 
   // Received  7 true 
   // Received  8 true 
   // Received  9 true

				
			

এখানে 0 থেকে 9 পর্যন্ত প্রিন্ট হয়েছে, যখন ok ভ্যালু মিথ্যা পেয়েছে তখনই চ্যানেল বন্ধ করে দিয়েছে। main() ফাংশনে ok মিথ্যা হওয়ার সাথে সাথেই break করে দিয়েছে।