Go یک زبان مختصر، ساده، ایمن و سریع با ویژگی های همزمانی برجسته است. الان هم ژنریک دارد. آیا وقت آن نرسیده است که Go را امتحان کنید؟
- برشها
- نقشهها
- ساختارها و روشها
- رابط
- سوئیچ
- برنامههای آموزشی
- کانالها
- محدوده و بستن
- انتخاب
- الگوهای همزمانی در Go
Go یک زبان برنامه نویسی متن باز از Google است که ساختن نرم افزار ساده، قابل اعتماد و کارآمد را آسان می کند. این بخشی از سلسله زبان های برنامه نویسی است که با فرآیندهای متوالی ارتباط تونی هور آغاز شد و شامل Occam، Erlang، Newsqueak و برزخ. پروژه زبان Go در حال حاضر بیش از ۱۸۰۰ مشارکت کننده دارد و توسط راب پایک، یک مهندس برجسته در Google.
Go در ابتدا به عنوان جایگزینی برای C++ توسعه یافت. اساسا، Pike از مجموعههای طولانی C++ یک برنامه اصلی Google خسته شده است< /a>. با این حال، همانطور که مشخص شد، تعداد بسیار کمی از تبدیلهای زبان Go از C++ دریافت شدهاند. وقتی از او پرسیده شد که پس از عرضه Go در سال ۲۰۱۲ چه چیزی او را بیشتر شگفت زده کرد، Pike پاسخ داد: “اگرچه انتظار داشتیم برنامه نویسان ++C Go را به عنوان یک جایگزین ببینند، در عوض بیشتر برنامه نویسان Go از زبان هایی مانند Python و Ruby می آیند.”
در آن زمان، تقریباً همه کسانی که از C++ یا جاوا میآمدند، میخواستند Go کلاسها و ژنریک داشته باشد. پایک و دیگران عقب نشینی کردند، اما در سال ۲۰۲۲، همه چیز تغییر کرد. از Go 1.18، کلیات در نهایت بخشی از زبان Go هستند.
این مقاله برخی از ویژگیهای متمایز Go را نشان میدهد، از جمله الگوهای همزمانی بسیار سبک و انواع جدید.
برش ها
Go ایده آرایهها را با برشها گسترش میدهد که دارای اندازه متغیر هستند. یک برش به آرایه ای از مقادیر اشاره می کند و یک طول را شامل می شود. به عنوان مثال، [ ]T
یک برش با عناصر از نوع T
است. در کد زیر، ما از برشهای برش بایتهای بدون علامت برای نگهداری پیکسلهای تصویری که تولید میکنیم استفاده میکنیم، جایی که مقادیر پیکسل از ۰ تا ۲۵۵ متغیر است. برنامههای Go با package main شروع به اجرا میکنند.
. عبارت import
یک نسخه توسعه یافته از عبارت include
C و C++ است.
package main
import "code.google.com/p/go-tour/pic"
func Pic(dx, dy int) [][]uint8 {
slice := make([][]uint8, dy)
for i := range slice {
slice[i] = make([]uint8, dx)
for j := range slice[i] {
slice[i][j] = uint8(i * j)
}
}
return slice
}
func main() {
pic.Show(Pic)
}
نحو :=
یک متغیر را اعلام و مقداردهی اولیه می کند، و کامپایلر هر زمان که بتواند یک نوع را استنتاج می کند. همچنین توجه داشته باشید که make
برای ایجاد برش ها و انواع دیگر استفاده می شود. یک حلقه for...range
معادل حلقه for...in
سی شارپ است. الگوی نشان داده شده در شکل ۱ با عبارت موجود در حلقه داخلی بالا تعیین می شود: (i*j)
. به بسته عکس و کد منبع برای کسب اطلاعات بیشتر.
شکل ۱. الگویی که برش ها را در Go نشان می دهد.
نقشه ها
عبارت Go map
کلیدها را به مقادیر نگاشت می کند. مانند slice
، شما یک نقشه با make
ایجاد می کنید، نه new
. در مثال زیر، کلیدهای رشته ای را به مقادیر صحیح نگاشت می کنیم. این کد درج، بهروزرسانی، حذف و آزمایش عناصر نقشه را نشان میدهد.
package main
import "fmt"
func main() {
m := make(map[string]int)
m["Answer"] = 42
fmt.Println("The value:", m["Answer"])
m["Answer"] = 48
fmt.Println("The value:", m["Answer"])
delete(m, "Answer")
fmt.Println("The value:", m["Answer"])
v, ok := m["Answer"]
fmt.Println("The value:", v, "Present?", ok)
}
در اینجا خروجی چاپ برنامه است:
The value: 42
The value: 48
The value: 0
The value: 0 Present? false
ساختارها و روشها
زبان Go فاقد کلاس است اما دارای یک struct
است که دنباله ای از عناصر نامگذاری شده است که fields
نامیده می شوند. هر فیلد
یک نام
و یک نوع
دارد. روش
تابعی با گیرنده است. یک اعلان method
یک شناسه (نام روش) را به یک متد متصل می کند و روش را با نوع پایه گیرنده مرتبط می کند.
در این مثال، ما یک راس
struct
را اعلام میکنیم که حاوی دو فیلد میزان شناور
، X و Y و یک متد، Abs
. فیلدهایی که با حروف بزرگ شروع می شوند عمومی هستند. فیلدهایی که با حروف کوچک شروع می شوند خصوصی هستند. فیلدها و روشها از طریق علامت نقطه (.
) و علامتگذاری (&
) نشانگر نشانگر هستند، مانند C. این برنامه ۵
را چاپ میکند.
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := &Vertex{3, 4}
fmt.Println(v.Abs())
}
رابط ها
یک نوع رابط با مجموعه ای از روش ها تعریف می شود. مقداری از نوع interface
می تواند هر مقداری را که آن متدها را پیاده سازی می کند، در خود جای دهد. در این مثال بعدی، یک رابط، Abser
و یک متغیر (a
) از نوع Abser
:
تعریف میکنیم.
package main
type Abser interface {
Abs() float64
}
func main() {
var a Abser
f: MyFloat(-math.Sqrt2
v = Vertex{3, 4}
a = f // a MyFloat implements Abser
a = &v // a *Vertex implements Abser
// In the following line, v is a Vertex (not *Vertex)
// and does NOT implement Abser.
a = v
fmt.Println(a.Abs())
}
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
type Vertex struct {
X, Y float64
}
توجه داشته باشید که تکالیف a=f
و a=&v
کار میکنند، اما تخصیص a=v
حتی کامپایل نمیشود. روش Abs
از Rertex
که در بخش قبل مشاهده کردید، برای گیرنده خود نشانگر به نوع Vertex
دارد. بنابراین، یک *Vertex
Abser
را پیادهسازی میکند، اما یک Vertex
این کار را نمیکند.
سوئیچ
عبارت switch
در Go مشابه عبارت switch
در سایر زبانهای مشابه C است، با این تفاوت که عبارتهای case
میتوانند انواع باشند. یا عبارات علاوه بر مقادیر ساده. موارد به طور خودکار شکسته می شوند مگر اینکه با عبارت fallthrough
به پایان برسند. موارد به ترتیبی که تعریف شده اند ارزیابی می شوند.
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Print("Go runs on ")
switch os = runtime.GOOS; os {
case "darwin":
fmt.Println("macOS.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.", os)
}
}
مجموعه ها
گوروتینها اساساً رشتههای بسیار سبک وزنی هستند که بر اساس روح فرآیندهای متوالی ارتباطی تونی هور است. در مثال زیر، خط اول func main
تابع say
را به صورت ناهمزمان فراخوانی می کند، در حالی که خط دوم آن را به صورت همزمان فراخوانی می کند. تفاوت در استفاده از واجد شرایط go
برای گوروتین ناهمزمان است:
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
مجموعهها، کانالها و عبارتهای select
هسته اصلی همزمانی بسیار مقیاسپذیر Go هستند که یکی از قویترین نقاط فروش زبان است. Go همچنین دارای اشیاء همگام سازی معمولی است، اما به ندرت مورد نیاز هستند. این برنامه خروجی می دهد:
hello
world
hello
world
hello
world
hello
world
hello
کانالها
کانالها در Go مکانیزمی برای اجرای همزمان توابع برای برقراری ارتباط با ارسال و دریافت مقادیر از یک نوع عنصر مشخص ارائه میکنند. این یک مثال است:
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // send sum to c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
}
توجه داشته باشید که مقدار کانال بدون مقدار اولیه صفر است. c = make(chan int)
یک کانال دو طرفه از اعداد صحیح ایجاد می کند. همچنین میتوانیم کانالهای ارسال یک طرفه (<-c
) و دریافت (c<-
) ایجاد کنیم. پس از آن، sum
را به صورت ناهمزمان با برش هایی از نیمه اول و دوم a
فراخوانی می کنیم. سپس، متغیرهای عدد صحیح x
و y
این دو جمع را از کانال دریافت می کنند. در عبارت "for _, v range a
"، زیرخط (_
)، شناسه خالی، به معنای نادیده گرفتن اولین مقدار نتیجه از برای است. حلقه ..range
که ایندکس است. خروجی برنامه ۱۷ -۵ ۱۲ است.
محدوده و بستن
در این مثال، میبینیم که چگونه یک فرستنده میتواند یک کانال را بسته
تا نشان دهد که هیچ مقدار دیگری ارسال نخواهد شد. گیرنده ها می توانند با تخصیص پارامتر دوم به عبارت دریافت، بسته شدن کانال را بررسی کنند.
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
حلقه for
در خط سوم main
(for i := محدوده c
) به طور مکرر مقادیر را از کانال دریافت می کند تا زمانی که بسته cap
ظرفیت کانال است که اندازه بافر در کانال است. هنگام ایجاد کانال، مانند خط اول main
، cap
به عنوان آرگومان دوم اختیاری تنظیم می شود. به شکل فشرده عبارات انتساب در تابع fibonacci
توجه کنید. خروجی برنامه ۱۰ مقدار اول سری فیبوناچی، ۰ تا ۳۴ است.
انتخاب کنید
یک عبارت select
انتخاب میکند که کدام یک از مجموعهای از عملیات احتمالی ارسال
یا دریافت
ادامه یابد. به نظر شبیه یک عبارت switch
است، اما همه موارد مربوط به عملیات ارتباطی است. یک select
مسدود میشود تا زمانی که یکی از موارد آن اجرا شود، سپس آن مورد را اجرا میکند. اگر چند مورد آماده باشد، یکی را بهطور تصادفی انتخاب میکند.
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
در اینجا، تابع main
تابع fibonacci
را با دو کانال بافر نشده، یکی برای نتایج و دیگری برای سیگنال خروج
فراخوانی میکند. تابع fibonacci
از عبارت select
برای منتظر ماندن در هر دو کانال استفاده می کند. تابع go
ناشناس و ناهمزمان که از خط سوم main
شروع میشود، منتظر میماند تا مقادیر (<-c)
را دریافت کند، سپس آنها را چاپ میکند. پس از ۱۰ مقدار، کانال quit
را تنظیم می کند، بنابراین تابع fibonacci
متوقف می شود.
الگوهای همزمانی در Go
ما چند ویژگی متمایزکننده زبان Go را بررسی کردهایم. حال، بیایید ببینیم که آنها چگونه با هم در مثال های برنامه نویسی کار می کنند. ما با چند الگوی همزمانی در Go شروع میکنیم که هر دو از سخنرانی راب پایک در سال ۲۰۱۲ در مورد الگوهای همزمانی گرفته شدهاند. در Go.
الگوی همزمانی شماره ۱: پنکه به داخل
در این مثال، ما از select
برای ایجاد یک گوروتین fan-in استفاده می کنیم که دو کانال ورودی رشته، input1
و input2
، در یک کانال خروجی بافر نشده، c
. عبارت select
به fanIn
اجازه میدهد تا به هر دو کانال ورودی به طور همزمان گوش دهد و هر کدام را که آماده است به کانال خروجی ارسال کند. مهم نیست که هر دو مورد از یک نام متغیر موقت برای نگه داشتن رشته از کانال های ورودی مربوطه خود استفاده می کنند.
package main
func fanIn(input1, input2 <-chan string) <-chan string {
c := make(chan string)
go func() {
for {
select {
case s := <-input1: <- s
case s := <-input2: c <- s
}
}
}()
return c
}
الگوی همزمانی شماره ۲: جستجوی موازی
این مثال همزمانی یک جستجوی موازی در اینترنت را پیادهسازی میکند، چیزی شبیه به آنچه که موتور جستجوی Google واقعاً انجام میدهد. برای شروع، replicas …Search
یک پارامتر متغیر برای تابع است. هر دو نوع جستجو
و نتیجه
در جای دیگری تعریف شده اند:
package main
func First(query string, replicas ...Search) Result {
c := make(chan Result)
searchReplica := func(i int) { c <- replicas[i](query) }
for i := range replicas {
go searchReplica(i)
}
return <-c
}
تماس گیرنده تعداد معینی از (N) توابع سرور جستجو را به تابع First
ارسال می کند. تابع First
یک کانال به نام c
برای نتایج ایجاد می کند و تابعی را برای پرس و جو از سرور i
تعریف می کند، سپس آن را در ذخیره می کند. searchReplica
. ابتدا
سپس searchReplica
را به صورت ناهمزمان برای همه سرورهای N فراخوانی میکند و همیشه پاسخ را در کانال c
برمیگرداند. اولین نتیجه را که از سرورها باز می گردد برمی گرداند.
بستهها در Go
بعد، به چند بسته نگاه می کنیم.
بسته http
بسته Go net/http
، اجرای سرویس گیرنده و سرور HTTP را ارائه میکند. این مثال یک وب سرور ساده را پیاده سازی می کند که محتویات دایرکتوری /usr/share/doc
را به یک سرویس گیرنده وب برمی گرداند:
package main
import (
"log"
"net/http"
)
func main() {
// Simple static webserver:
log.Fatal(http.ListenAndServe(":8080", http.FileServer(http.Dir("/usr/share/doc"))))
}
این مثال در محیط آنلاین Go Playground به درستی کار نمی کند. با این حال، اگر آن را روی خط فرمان Mac اجرا کنید، موارد زیر را به یک مرورگر وب باز میگرداند که http://localhost:8080/ را درخواست میکند. :
bash/
ccid/
فنجان/
groff/
ntp/
postfix/
بسته الگو
بسته html/template
الگوهای مبتنی بر داده را برای تولید خروجی HTML اجرا می کند که در برابر تزریق کد ایمن است. به عنوان مثال:
package main
import "html/template"
func main() {
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")
}
کد بالا خروجی HTML ایمن و فراری تولید می کند:
Hello, <script> alert(' you have been pwned')</script>!
بدون فرار اضافه شده توسط بسته html/template
در این مثال، میتوانستیم رشته جاوا اسکریپت قابل اجرا زیر را تولید کنیم:
Hello, <script> alert(' you have been pwned')</script>!
عمومی در Go
بله، بالاخره، Go ژنریک دارد. آنها عمیقاً در زبان ادغام شدهاند و ایده محدودیتهای نوع را شامل میشوند. این بدان معناست که پارامترهای نوع یک تابع در بین براکت ها، قبل از آرگومان های تابع ظاهر می شوند. به طور کلی، یک تابع پیادهسازی شده با انواع عمومی کارآمدتر از تابعی است که با نوع any
پیادهسازی میشود، و نسبت به اجرای مجدد تابع برای هر نوع مورد علاقه، ضایعات کمتری دارد. دیدن گروهی از توابع در کد شما که فقط در انواع آرگومانهایشان متفاوت است، باید یک پرچم قرمز باشد تا بجای آن، یک نسخه عمومی بنویسید.
دو مثال زیر مربوط به بخش جدیدی از صفحه برنامه نویسی Tour of Go است.< /p>
پست های مرتبط
آموزش Golang: با زبان Go شروع کنید
آموزش Golang: با زبان Go شروع کنید
آموزش Golang: با زبان Go شروع کنید