9.4.7.1 توضیحات #
الگو Semaphore برای مدیریت کنترل دسترسی به منابع مشترک در همزمانی استفاده می شود. فرض کنید بصورت موازی ۱۰۰ درخواست HTTP سمت سرور میاد و I/O شبکه برای پردازش همزمان این ۱۰۰ درخواست درگیر میشود و به مرور عملکرد کاهش می یابد. حال اگر ما بیایم این ۱۰۰ درخواست موازی را تقسیم کنیم به ۵ دسته ۲۰ تایی که بصورت همزمانی انجام شود باعث می شود I/O شبکه کاهش یابد و عملکرد بهتری را خواهیم داشت.
حال برای اینکه بتوانیم این الگو را طراحی کنیم نیاز هست از کانال بافر شده استفاده کنیم.
به نقل از ویکی پدیا :
در علم رایانه نشانبر یا سمافور (به انگلیسی: Semaphore) به متغیری گفته میشود که در محیطهای همروند برای کنترل دسترسی فرایندها به منابع مشترک به کار میرود. سمافور میتواند به دو صورت دودویی (که تنها دو مقدار صحیح و غلط را دارا است) یا شمارنده اعداد صحیح باشد. از سمافور برای جلوگیری از ایجاد وضعیت رقابتی میان فرایندها استفاده میگردد. به این ترتیب، اطمینان حاصل میشود که در هر لحظه تنها یک فرایند به منبع مشترک دسترسی دارد و میتواند از آن بخواند یا بنویسد (انحصار متقابل)
سمافورها اولین بار بهوسیلهٔ دانشمند علوم رایانه هلندی، ادسخر دیکسترا معرفی شدند.[۱] و امروزه بهطور گستردهای در سیستم عاملها مورد استفاده قرار میگیرند.
9.4.7.2 دیاگرام #
9.4.7.3 نمونه کد #
1package main
2
3import (
4 "fmt"
5 "time"
6)
7
8type Semaphore interface {
9 Acquire()
10 Release()
11}
12
13type semaphore struct {
14 semCh chan struct{}
15}
16
17func New(maxConcurrency int) Semaphore {
18 return &semaphore{
19 semCh: make(chan struct{}, maxConcurrency),
20 }
21}
22
23func (s *semaphore) Acquire() {
24 s.semCh <- struct{}{}
25}
26
27func (s *semaphore) Release() {
28 <-s.semCh
29}
30
31func main() {
32 sem := New(3)
33 doneC := make(chan bool, 1)
34 totProcess := 10
35
36 for i := 1; i <= totProcess; i++ {
37 sem.Acquire()
38 go func(v int) {
39 defer sem.Release()
40 longRunningProcess(v)
41
42 if v == totProcess {
43 doneC <- true
44 }
45 }(i)
46 }
47
48 <-doneC
49}
50
51func longRunningProcess(taskID int) {
52 fmt.Println(time.Now().Format("15:04:05"), "Running task with ID", taskID)
53 time.Sleep(2 * time.Second)
54}
1$ go run main.go
223:00:00 Running task with ID 3
323:00:00 Running task with ID 1
423:00:00 Running task with ID 2
523:00:02 Running task with ID 6
623:00:02 Running task with ID 4
723:00:02 Running task with ID 5
823:00:04 Running task with ID 7
923:00:04 Running task with ID 8
1023:00:04 Running task with ID 9
1123:00:06 Running task with ID 10
در کد فوق ما یک اینترفیس به نام Semaphore داریم که ۲ تا متد Acquire و Release دارد که با استفاده از Acquire منابع را قبل از اینکه تابع logRunningProcess را فراخوانی کنیم قفل می کنیم و پس از اینکه عملیات تابع logRunningProcess انجام شد با استفاده از Release منابع را آزاد می کنیم.
یک تابع سازنده به نام New قرار دادیم که ظرفیت کانال Semaphore را مشخص می کند تا تعداد درخواستی که نیاز است همزمان انجام شود چندتا باشد.
زمانیکه ما متد Acquire را فراخوانی می کنیم {}{}struct را به کانال می فرستیم تا زمانیکه ظرفیت پر نشده است. پس از اینکه ظرفیت کانال پر شود آن بخش از کد ما Acquire را فراخوانی کردیم قفل می شود و تا زمانی که از کانال بواسطه متد Release دریافت نکنیم منابع در دسترس نخواهد بود.
9.4.7.4 کاربردها #
- مدیریت دسترسی به منابع مشترک
- همگام سازی دسترسی به ساختار داده مشترک
- مدیریت دسترسی به منابع محدود
- پیاده سازی یک Load Balancer
- پیاده سازی thread pool