Ngôn ngữ lập trình Go là một ngôn ngữ nguồn mở do Google phát triển, giúp bạn dễ dàng tạo ra các phần mềm đơn giản, ổn định và hiệu quả. Go là một phần của dòng ngôn ngữ lập trình Communication Sequential Processes (CSP) do Tony Hoare sáng lập, bên cạnh Go còn có Occam, Erlang, Newsqueak và Limbo. Bài viết này sẽ đề cập đến những tính năng đặc biệt của Go so với các ngôn ngữ lập trình phổ biến khác, với điểm nhấn vào tính năng concurrency, một tính năng rất hữu ích. Hiện nay, dự án Go có hơn 500 cộng tác viên, do ông Rob Pike (một kỹ sư giỏi tại Google) quản lý, ông từng làm việc tại Bell Labs trong đội Unix và đồng tác giả của Plan 9 và Inferno.
Ngôn ngữ Go và tính năng Concurrency
Go triển khai ý tưởng về slide trong mảng (array). Một slide trỏ tới một mảng giá trị và có một độ dài. []T
là một slide với các yếu tố của loại T
. Ví dụ, chúng ta có thể sử dụng slide của slide của byte chưa được gán để chứa giá trị pixel trong một ảnh mà chúng ta tự tạo ra. Với package main
, chương trình sẽ bắt đầu chạy. Trong Go, khai báo import
tương đương với khai báo include
trong C và C++. Trong ví dụ này, chúng ta đang lấy file pic từ repo Mercurial. Cú pháp :=
được sử dụng để khai báo và khởi tạo một biến, và trình biên dịch sẽ suy ra kiểu dữ liệu khi có thể. Tương tự, make
được sử dụng để tạo ra slide và một số loại dữ liệu khác. Vòng lặp for..range
tương đương với vòng lặp for..in
của C#.
Khái niệm về Map trong Go
Trong Go, khai báo map
cho phép gán một khoá (key) đến một giá trị. Chúng ta có thể tạo một map bằng cách sử dụng hàm make
, không cần sử dụng new
. Trong ví dụ này, chúng ta gán khoá kiểu string và giá trị kiểu số nguyên (integer). Dưới đây là ví dụ về việc chèn, cập nhật, xoá và kiểm tra thành phần trong map. Kết quả sẽ được xuất ra như sau:
The value: 42
The value: 48
The value: 0
The value: 0
Present? false
Cấu trúc và phương thức trong Go
Ngôn ngữ Go không có lớp (class) nhưng lại có cấu trúc (struct), đây là một chuỗi các thành phần được đặt tên, gọi là trường (field), mỗi trường có một tên và một kiểu dữ liệu. Một phương thức (method) là một hàm với một đối tượng nhận (receiver). Một khai báo phương thức liên kết một tên phương thức với một phương thức và liên kết phương thức này với kiểu dữ liệu cơ bản của đối tượng nhận. Trong ví dụ này, chúng ta khai báo một cấu trúc Vertex để lưu trữ 2 trường floating point X và Y, và một phương thức Abs. Các trường bắt đầu bằng chữ cái viết hoa là công khai (public), trong khi các trường bắt đầu bằng chữ cái viết thường là ẩn (private). Cả trường và phương thức có thể được truy cập thông qua ký hiệu *
và &
cho con trỏ giống như trong C. Kết quả của chương trình sẽ là 5.
Khái niệm Interface trong Go
Interface là một tập hợp các phương thức. Một giá trị của interface có thể chứa bất kỳ giá trị nào thỏa mãn các phương thức đã được định nghĩa. Trong ví dụ này, chúng ta định nghĩa một interface Abser và một biến a
có kiểu Abser. Lưu ý rằng việc gán giá trị ở dòng 17 và 18 là hợp lệ, nhưng việc gán ở dòng 22 sẽ không biên dịch được. Phương thức Abs của cấu trúc Vertex mà chúng ta đã thấy ở ví dụ trước đó có một con trỏ đến kiểu Vertex làm đối tượng nhận của nó, do đó *Vertex thỏa mãn Abser, trong khi Vertex lại không thỏa mãn.
Câu lệnh switch trong Go
Câu lệnh switch trong Go tương tự như câu lệnh switch trong các ngôn ngữ C khác, ngoại trừ việc các câu lệnh case
có thể là kiểu hoặc biểu thức đến các giá trị đơn giản, và các câu lệnh này sẽ tự động kết thúc trừ khi sử dụng fallthrough
. Các câu lệnh này sẽ được đánh giá theo thứ tự được khai báo.
Goroutine và Channel trong Go
Goroutine có thể được coi là đặc trưng của Communicating Sequential Processes (CSP) do Tony Hoare sáng lập, với tính năng rất nhẹ nhàng. Ví dụ, dòng 16 trong ví dụ trên gọi hàm say
mà không đồng thời với hàm say
ở dòng 17. Goroutine, kênh channel và câu lệnh select là những thành phần cốt lõi của Go để mở rộng tính đồng thời, đây là một trong những điểm mạnh của ngôn ngữ này. Go cũng có các đối tượng đồng bộ truyền thống, nhưng hiếm khi cần sử dụng chúng. Kết quả của chương trình sẽ là:
hello world
hello world
hello world
hello world
hello
Kênh channel trong Go
Kênh channel trong Go cung cấp cơ chế thực thi các hàm đồng thời để giao tiếp bằng cách gửi và nhận các giá trị của một kiểu cụ thể. Một kênh channel khi chưa được khởi chạy được coi là nil
. Ở dòng 16, chúng ta tạo một kênh channel hai chiều với kiểu số nguyên. Chúng ta cũng có thể tạo một kênh gửi đẳng hướng -c
và kênh nhận đẳng hướng c-
. Ở dòng 17 và 18, chúng ta gọi hàm sum
bất đồng bộ với phân đoạn đầu và cuối của a
. Ở dòng 19, các biến số nguyên x
và y
nhận hai tổng từ kênh này. Ở dòng 7, dấu gạch dưới _
được sử dụng để bỏ qua giá trị kết quả đầu tiên của vòng lặp for..range
, tức là chỉ mục index. Kết quả của chương trình sẽ là:
17 -5 12
Vòng lặp Range và Close trong Go
Người gửi có thể đóng (close) một kênh channel để cho chương trình biết rằng không còn giá trị nào được gửi nữa. Bộ nhận (receiver) có thể kiểm tra xem một kênh channel đã được đóng hay chưa bằng cách gán một tham số thứ hai trong khai báo nhận. Một vòng lặp for i := range c
sẽ tiếp tục nhận các giá trị từ kênh cho đến khi kênh đó bị đóng. Dung lượng (capacity) của kênh là kích thước bộ đệm trong kênh, và đây là một tham số tùy chọn mà bạn có thể thiết lập cho kênh như ở dòng 17. Lưu ý rằng cú pháp ngắn gọn được sử dụng trong khai báo giá trị của hàm fibonacci
. Kết quả của chương trình sẽ là 10 giá trị đầu tiên trong chuỗi Fibonacci, từ 0 đến 34.
Lựa chọn Select trong Go
Câu lệnh select
trong Go cho phép chọn một tập send
hoặc receive
để thực hiện. Đây là một câu lệnh chặn cho đến khi một trong các case
của nó có thể thực hiện được, sau đó nó thực thi case
đó. Nếu có nhiều case
có thể chạy được, thì select
sẽ chọn một case
ngẫu nhiên. Trong ví dụ này, hàm main
gọi hàm fibonacci
với hai kênh channel không có bộ đệm - một kênh cho kết quả và một kênh cho tín hiệu quit. Hàm fibonacci
sử dụng câu lệnh select
để chờ cả hai kênh. Hàm go
được sử dụng để bắt đầu chạy bất đồng bộ ở dòng 21, sau đó chờ để nhận giá trị ở dòng 23 và in kết quả. Sau khi nhận 10 giá trị, nó chạy kênh quit, và do đó hàm fibonacci
biết khi nào phải dừng lại.
Mẫu chạy đồng thời - Ví dụ 1
Trong ví dụ này, chúng ta sử dụng câu lệnh select
để tạo ra một fanIn goroutine
kết hợp hai kênh nhập chuỗi, input1
và input2
, và gửi vào một kênh xuất không có bộ đệm là c
. Câu lệnh select
cho phép fanIn
lắng nghe cả hai kênh nhập cùng lúc và xử lý để chuẩn bị cho kênh xuất. Điều đáng chú ý là cả hai case
trong ví dụ này đều sử dụng cùng một biến tạm để lưu trữ chuỗi từ kênh nhập. Ví dụ này xuất hiện trong Concurrency Pattern của Rob Pike năm 2012.
Mẫu chạy đồng thời - Ví dụ 2
Ví dụ này thiết lập một tìm kiếm song song trên internet, tương tự như Google hiện nay. Ban đầu, replicas...Search
là một tham số động được gắn với hàm này; cả Search
và Result
là các loại được định nghĩa tùy chỗ. Hàm gọi caller
chuyển các hàm máy chủ tìm kiếm N sang hàm First
, trong hàm này tạo ra một kênh c
chứa các kết quả và định nghĩa một hàm yêu cầu máy chủ thứ i
, sau đó lưu nó vào searchReplica
. Tiếp theo, First
gọi searchReplica
bất đồng bộ với mọi máy chủ N, luôn luôn trả về kết quả vào kênh c
, và trả về kết quả đầu tiên ngược lại từ các máy chủ N. Ví dụ này cũng xuất hiện trong Concurrency Pattern của Rob Pike năm 2012.
Gói http
trong Go
Gói net/http
trong Go cung cấp chức năng thiết lập HTTP client và server. Ví dụ này thiết lập một máy chủ web cơ bản, trả về nội dung của thư mục /usr/share/doc
trên trình duyệt phía client. Ví dụ này không chạy được trên môi trường trực tuyến Go Playground, nhưng chỉ chạy trên dòng lệnh Mac, và sẽ trả về một trình duyệt web với địa chỉ http://localhost:8080/:
bash/
ccid/
cups/
groff/
ntp/
postfix/
Gói template
trong Go
Gói html/template
trong Go cho phép tạo và trình diễn các mẫu template an toàn cho HTML, ngăn chặn việc chèn mã độc. Sử dụng gói template này, ví dụ trên có thể tạo ra một chuỗi ký tự JavaScript có thể chạy được, Hello, !