Hướng dẫn dùng golang time.sleep trong PHP

Một trong các điểm mạnh của Go đó là khả năng sử lý đa luồng nó là một trong những vấn đề được các nhà phát triển golang chú trọng hàng đầu. Go đưa ra 2 tính năng hỗ trợ concurrency rất mạnh đó là Goroutine và Channel. Ở bài viết này chúng ta sẽ tập trung vào Goroutine trong Go. Trước tiên cùng đi qua các khái niệm 1 chút.!

1. Concurrency

Xử lý concurrency (đồng thời) nghĩa là có khả năng giải quyết nhiều công việc một lúc, và những công việc đó ko nhất thiết phải xảy ra tại cùng một thời điểm. Hay nói ngắn gọn hơn là việc nhiều task được xử lý cùng một lúc.

2. Goroutines

Cơ chế hoạt động và cách sử dụng

Cơ chế của goroutine khá đơn giản: 1 function tồn tại một cách đa luồng với các goroutine khác trên cùng một không gian bộ nhớ, Go có bộ điều khiển quản lý các goroutine, rồi phân phối chúng vào các bộ xử lý logic và gắn mỗi bộ xử lý logic này với một thread hệ thống được tạo ra trước đó để thực thi các goroutine này. Nói cách khác, mỗi thread hệ thống sẽ xử lý một nhóm goroutine được điều phối thông qua bộ xử lý logic. Với bộ điều khiển quản lý tác vụ đồng thời và cơ chế bộ xử lý logic, những cái khó khăn, phức tạp khi khai báo thread Go đã xử lý hết giúp chúng ta rồi, chúng ta chỉ việc sử dụng thôi.

Để khởi tạo một goroutine ta chỉ cần thêm phía trước một function call hay method call từ khoá go . VD: Hiển thị số tuần tự từ 1 đến 20 và không sử dụng goroutine.

package main

import (
	"fmt"
)

func main() {
	g1()
	g2()
}

func g1() {
	for i := 1 ; i <= 10 ; i++ {
		fmt.Println(i)
	}
}

func g2() {
	for i := 11 ; i <= 20 ; i++ {
		fmt.Println(i)
    }
}
Hiển thị số từ 1 đến 20

ví dụ trên có 2 hàm là
g1() : hiển thị các số từ 1 đến 10
g2() : hiển thị các số từ 11 đến 20.

Do hàm g1() viết trước hàm g2() nên g1() sẽ thực thi trước g2(), khi đó g2() sẽ phải chờ g1() thực thi xong và kết quả sẽ hiển thị các số từ 1 đến 20 một cách tuần tự.

Hướng dẫn dùng golang time.sleep trong PHP
Kết quả sau khi chạy chương trình

VD: hiển thị số từ 1 đến 20 sử dụng goroutines

package main

import (
	"fmt"
	"time"
)

func main() {
	go g1()
	go g2()
	time.Sleep(time.Second)
}

func g1() {
	for i := 1; i <= 10; i++ {
		go fmt.Println(i)
	}
}

func g2() {
	for i := 11; i <= 20; i++ {
		go fmt.Println(i)
	}
}

Ví dụ trên có 2 hàm là g1() và g2() ta thêm từ khóa go đằng trước tên hàm để thể hiện đây là hàm goroutine, khi đó 2 hàm g1() và g2() sẽ được thực thi đồng thời mà không cần phải chờ hàm trước nó thức thi xong, ta thêm từ khóa go vào trước các câu lệnh fmt.Println(i)fmt.Println(j)để cho chương trình biết các lệnh này cũng được thực thi đồng thời.
lệnh time.Sleep(time.Second) là để chương trình main sleep 1 giây chờ 2 hàm g1(), và g2() kết thúc. Và đây là kết quả của chương trình trên.

Hướng dẫn dùng golang time.sleep trong PHP
Chương trình chạy sau khi sử dụng goroutines

Mỗi lần chạy, các kết quả, thứ tự các chữ số cho ra là khác nhau.

Ưu điểm khi sử dụng goroutines

  • Goroutine rẻ hơn khi so sánh với một thread. Chúng chỉ chiếm vài kb trong kích thước stack và stack có thể tăng hoặc co lại tuỳ theo yêu cầu của ứng dụng, trong khi đó với trường hợp của thread kích thước stack sẽ được chỉ định và cố định
  • Thời gian khởi động của Goroutines nhanh hơn so với Thread
  • Giao tiếp giữa các Goroutines sử dụng channel cực an toàn.
  • Goroutines và OS thread không tồn tại 1:1. Mỗi Goroutines có thể chạy đồng thời nhiều threads, chúng có thể ghép vào OS threads.

Kết bài

Trên đây là bài giới thiệu cơ bản nhất về goroutines, tuỳ vào mục đích của chương trình mà các bạn có thể quyết định có sử dụng goroutines hay không.

Hế nhô các bạn, sau một thời gian làm việc với NodeJS thì mình biết đến go một cách tình cờ qua một lần đi phỏng vấn. Và bây giờ mình đã đến với Go bằng tất cả tâm tư, cũng như những kinh nghiệm khi mình đã làm việc với NodeJS

  10 Add-on Google Sheets phải có dành cho các Recruiters

  10 Công cụ Go-To Tech dành riêng cho các Software Developer

Ngày Đầu học Go, đọc đâu cũng thấy Go Channel is awesome, Go is concurrency, the modern programming language…, thật là thú vị, google vài vòng mình cx nhanh chóng nhận ra một điểm mà Go làm tốt hơn NodeJS là create Worker process, channel và goroutine là nguyên nhân khiến việc kiểm soát Worker dễ hơn NodeJS rất nhiều, ví dụ để giới hạn n Worker cùng nhau chạy đồng thời chúng ta chỉ cần tạo cho nó một buffered channel sau đó chạy n Worker trong n goroutine.

Hướng dẫn dùng golang time.sleep trong PHP
Hướng dẫn dùng golang time.sleep trong PHP

// create a buffered channel
var UploadPool chan WorkerMessage

// worker subcribe to channel and do something with it Sleep for example
func Worker(UploadPool chan WorkerMessage){
  for {
	workerMessage := <- UploadPool
   	time.Sleep(time.Second)		
  }
}
// init function to run n worker in goroutine
func InitWorker(numWorker int, UploadPool chan WorkerMessage){
	for i:=1;i<=numWorker;i++{
		go Worker(UploadPool)
	}
}

Và tiếp theo đây, nhân dịp mình đang dùng NodeJS để xử lí upload ảnh, và resize ảnh nên mình sẽ làm điều tương tự với GoLang để xem chúng nó có gì hay.

Follow Chart

Hướng dẫn dùng golang time.sleep trong PHP
Hướng dẫn dùng golang time.sleep trong PHP

Nào cùng bắt đầu với Follow 1: Lắng nghe form-data request từ front end và save file to local. Mình sẽ sử dụng Gin Framework, một web framework nhiều sao nhất trên github.

func HandleUploadForm(c *gin.Context) {
	form,_ := c.MultipartForm()
	files := form.File["files"]
	var fileNames []string
	for _, file := range files {
		fileName :=fmt.Sprintf("%v",uuid.New()) + file.Filename
		fileNames = append(fileNames,fileName)
		c.SaveUploadedFile(file, "storage/image/"+fileName)
	}
	c.JSON(200, utils.ApiResponse{
		Ok:      true,
		Message: "Uploaded successfully.....",
		Data:    fileNames,
	})
	for _,fileName := range fileNames{
    // loop and push WorkerMessage to UploadPool for Worker.
		UploadPool <- WorkerMessage{FileName: fileName,Resize:true}
	}
}

Như trong đoạn code đầu chúng ta đã có n Worker chạy concurrency và đang trong trang thái chờ nhận file để upload lên GCloud. Thêm cho nó 1 function để nó có thể upload lên GCloud nào. Thêm channel finished cho mỗi goroutine để có thể hứng được kết quả của mỗi task upload, tuỳ vào kết quả và theo logic của mỗi người mà chúng ta sẽ xử lí tiếp.

func uploadToGCloudStorage(fileName string, finished chan bool) {
	f, err := os.Open("storage/image/" + fileName)
	if err != nil {
		utils.Logger("error", err)
		return
	}
	defer f.Close()
	object := Bucket.Object(fileName)
	wc := object.NewWriter(context.Background())
	if _, err = io.Copy(wc, f); err != nil {
		utils.Logger("error", err)
		return
	}
	if err := wc.Close(); err != nil {
		utils.Logger("error", err)
		return
	}
	fmt.Println(utils.ApplyStyle("bold", "yellow", "Upload to Cloud successfully...."))
	object.ACL().Set(context.Background(),storage.AllUsers,storage.RoleReader)
	objectAddress := ObjectAddress{
		ID:primitive.NewObjectID(),
		FileName: fileName,
	}
	_,err = database.Models.Object.InsertOne(context.Background(),objectAddress)
	if err != nil {
		utils.Logger("error",err)
	}
	finished <- true
}


func UploadImageTaskConsumer(UploadPool chan WorkerMessage){
	for {
		workerMessage := <- UploadPool
		finished := make(chan bool)
		if workerMessage.Resize == true {
			go ImageAnalysis(ObjectAddress{
				FileName: workerMessage.FileName,
			})
		}
		go uploadToGCloudStorage(workerMessage.FileName,finished)
		<- finished
	}
}

Vậy là xong vụ upload file, so sánh với NodeJS thì thay vì phải quản lí Worker rất vất vả nodejs thì giờ đây, chúng ta có thể làm điều đó dễ dàng với Go Channel.

Các bạn có thể xem responstory của mình tại đây, mình mong được các bạn góp ý để cải thiện bản thân, trở nên tốt hơn.