3.9 تکنیک های همزمانی

3.9 تکنیک های همزمانی

3.9.1 ارسال سیگنال انجام شدن با کانال ساختار #

شما می توانید با استفاده از کانال struct سیگنالی برای تکمیل شدن انجام یک کار را بفرستید.

 1package main
 2
 3import (
 4	"fmt"
 5	"time"
 6)
 7
 8func task1(done chan struct{}) {
 9	// Do some work here
10	fmt.Println("doing task 1")
11
12	time.Sleep(2 * time.Second)
13
14	fmt.Println("task 1 has been completed")
15	done <- struct{}{}
16}
17
18func task2(done <-chan struct{}) {
19	select {
20	case <-done:
21		// Do some work here
22		fmt.Println("doing task 2")
23		time.Sleep(2 * time.Second)
24		fmt.Println("task 2 has been completed")
25	}
26}
27
28func main() {
29	done := make(chan struct{})
30
31	go task1(done)
32	go task2(done)
33
34	time.Sleep(5 * time.Second)
35	fmt.Println("all tasks has been completed")
36}
1$ go run main.go
2doing task 1
3task 1 has been completed
4doing task 2
5task 2 has been completed
6all tasks has been completed

در کد فوق ما دو تابع به نام task1 و task2 داریم که داخل گوروتین هستند و به عنوان پارامتر ورودی یک کانال ساختار خالی میگیرد حال کانال ساختار را به هر دو تابع پاس دادیم در ابتدا task1 فرآیند خود را انجام می دهد و سپس سیگنال انجام شدن را می فرستد و در ادامه تابع task2 سیگنال را دریافت می کند و فرآیندهای خود را انجام می دهد.

در زبان گو شما وقتی یک struct کاملا خالی و بدون فیلد میسازید هیچ تخصیص حافظه‌ای برایش صورت نمیگیرد.

3.9.2 بررسی یک سرویس یا فرآیند با heartbeat #

شما با استفاده از همزمانی می توانید یک heartbeat پیاده سازی کنید تا یک سرویس یا فرآیند را بطور مداوم طی بازه زمانی مشخصی زیر نظر داشته باشد و از وضعیت آن سرویس یا فرآیند به شما اطلاع دهد.

 1package main
 2
 3import (
 4	"fmt"
 5	"time"
 6)
 7
 8func heartbeat(interval time.Duration, c chan<- struct{}) {
 9    ticker := time.NewTicker(interval)
10	for {
11		select {
12		case <-ticker.C:
13			c <- struct{}{}
14		}
15	}
16}
17
18func task() {
19	// Do some work here
20	fmt.Println("Task running...")
21}
22
23func main() {
24	c := make(chan struct{})
25	go heartbeat(1*time.Second, c)
26
27	for {
28		select {
29		case <-c:
30			task()
31		}
32	}
33}
1$ go run main.go
2Task running...
3Task running...
4Task running...
5Task running...

در کد فوق ما یک تابع به نام heartbeat ایجاد کردیم که طی مدت زمانی یک سیگنال می فرستد تا تابع task اجرا شود که وضعیت فرآیند یا سرویس را گزارش دهد. ما مدت زمان را ۱ ثانیه گذاشتیم و یک کانال ساختار ایجاد کردیم و به تابع heartbeat که داخل یک گوروتین هست پاس دادیم سپس هر ۱ ثانیه از طریق کانال c ما سیگنال اجرای task برای بررسی وضعیت سرویس یا فرآیند را دریافت میکنیم.

3.9.3 ارسال درخواست‌های تکراری به سرور یا سرویسی #

فرض کنید شما نیاز دارید به یک سرور یا سرویسی چندین درخواست تکراری را بصورت موازی بفرستید که به اینکار replicated requests می گویند.

به مثال زیر توجه کنید :

 1package main
 2
 3import (
 4    "fmt"
 5    "net/http"
 6)
 7
 8func makeRequest(url string, c chan<- *http.Response) {
 9    resp, err := http.Get(url)
10    if err != nil {
11        c <- nil
12    } else {
13        c <- resp
14    }
15}
16
17func main() {
18    urls := []string{"http://example.com", "http://example.org", "http://example.net"}
19
20    c := make(chan *http.Response)
21    defer close(c)
22    for _, url := range urls {
23        go makeRequest(url, c)
24    }
25
26    for i := 0; i < len(urls); i++ {
27        resp := <-c
28        if resp == nil {
29            fmt.Println("Error making request")
30        } else {
31            fmt.Println(resp.Status)
32        }
33    }
34}
1$ go run main.go
2200 OK
3200 OK
4200 OK

در کد فوق ما یک تابع makeRequest داریم که ۲ تا پارامتر ورودی دارد اولین پارامتر url میگیرد و دومین پارامتر یک کانال فقط ارسال از نوع http.Response* میگیرد. سپس یک ریکوئست با متد GET ایجاد میکند و خروجی را داخل کانال میفرستد. در تابع main ما یک لیست url داریم که قرار است بصورت موازی به این آدرس ها درخواست بفرستیم و خروجی را دریافت کنیم در اینجا یک کانال از نوع http.Response* ایجاد کردیم و سپس یک حلقه for-range قرار دادیم و به ازای هر یک از url ها تابع makeRequest را فراخوانی کرده‌ایم و سپس داخل گوروتین قرار داده‌ایم، در نهایت یک حلقه for-i داریم که به تعداد url ها شمارش میکند و از طریق کانال ریسپانس را دریافت می کند.

3.9.4 بازیابی سلامتی یک گوروتین #

در برنامه نویسی همزمانی خیلی مهم است شما به سلامتی یک گوروتین رسیدگی کنید که اگر گوروتین قادر به انجام کاری نیست مجدد بتوانید سلامتی گوروتین را بازیابی کنید. در زبان گو شما می توانید با استفاده از پکیج context گوروتین هایی که وضعیت سالمی ندارد را با راه اندازی مجدد سلامتی آنها را بازیابی کنید.

 1package main
 2
 3import (
 4	"context"
 5	"fmt"
 6	"time"
 7)
 8
 9func unhealthyGoroutine(ctx context.Context) {
10	for {
11		select {
12		case <-ctx.Done():
13			fmt.Println("Goroutine is unhealthy, exiting")
14			return
15		default:
16			// Do some work here
17			fmt.Println("Goroutine running...")
18			time.Sleep(500 * time.Millisecond)
19		}
20	}
21}
22
23func main() {
24	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
25	defer cancel()
26
27	for {
28		go unhealthyGoroutine(ctx)
29		<-time.After(4 * time.Second)
30	}
31}
 1$ go run main.go
 2Goroutine running...
 3Goroutine running...
 4Goroutine running...
 5Goroutine running...
 6Goroutine running...
 7Goroutine running...
 8Goroutine is unhealthy, exiting
 9Goroutine is unhealthy, exiting
10Goroutine is unhealthy, exiting
11Goroutine is unhealthy, exiting
12Goroutine is unhealthy, exiting
13Goroutine is unhealthy, exiting

در کد فوق ما یک تابع به نام unhealthyGoroutine داریم که بصورت جداگانه در گوروتین های مختلف اجرا می شود و کاری را انجام می دهد. داخل تابع ما یک select داریم که در یکی از case هایش context.Done را بررسی میکنیم آیا فرآیند لغو شده است یا خیر. داخل تابع main ما یک context از نوع Timeout با مدت زمان ۳ ثانیه‌ای ایجاد کردیم و در ادامه داخل یک حلقه بینهایت تابع unhealthyGoroutine داخل گوروتین قرار دادیم و هر ۴ ثانیه یک نمونه از این تابع داخل گوروتین های مختلف اجرا می شود.

در اینجا کارهای داخل تابع unhealthyGoroutine انجام شود پس از ۳ ثانیه بواسطه context فرآیندها لغو می شود و از گوروتین خارج می شود. حال ما داخل تابع main اجازه دادیم یک گوروتین جدید و سالم را اجرا کند و جایگزین گوروتین ناسالم شود.

3.9.5 پیاده سازی الگوریتم فیبوناچی با همزمانی #

دنباله فیبوناچی مجموعه‌ای از اعداد است که در آن هر عدد حاصل جمع دو عدد قبلی است که معمولا با 0 و 1 شروع می شود. ، 55، 89، 144 و غیره. دنباله فیبوناچی به نام ریاضیدان ایتالیایی، لئوناردو پیزا، که به فیبوناچی نیز معروف بود، نامگذاری شده است. الگوریتم فیبوناچی روشی برای محاسبه عدد n در دنباله فیبوناچی است. روش‌های مختلفی برای پیاده‌سازی الگوریتم فیبوناچی وجود دارد، اما رایج‌ترین روش استفاده از بازگشت است.

در علوم و مهندسی کامپیوتر، از دنباله فیبوناچی برای تحلیل عملکرد الگوریتم هایی مانند برج هانوی و تکنیک جستجوی فیبوناچی استفاده می شود.

در زیر ما برای اینکه بتوانید در سریعترین حالت ممکن محاسبات را انجام دهیم خیلی ساده می توانیم از همزمانی و کانال استفاده کنیم و در کسری از ثانیه محاسبه کنیم.

 1package main
 2
 3import (
 4	"fmt"
 5)
 6
 7func fibo(n int) <-chan int {
 8	result := make(chan int)
 9	go func() {
10		defer close(result)
11
12		if n <= 2 {
13			result <- 1
14			return
15		}
16
17		result <- <-fibo(n-1) + <-fibo(n-2)
18
19	}()
20
21	return result
22}
23
24func main() {
25	fmt.Println(<-fibo(25))
26}
1$ go run main.go
275025