2 simple experimental examples to show how using go differs the whole thing and make it fast.
`package main
import (
"fmt"
"time"
)
func cook(dish string, seconds int) {
fmt.Printf("[%s] started cooking\n", dish)
time.Sleep(time.Duration(seconds) * time.Second)
fmt.Printf("[%s] done!\n", dish)
}
func main() {
start := time.Now()
cook("1080p", 3)
cook("720p", 2)
cook("480p", 1)
fmt.Printf("\n⏱ Total time: %v\n", time.Since(start).Round(time.Second))
fmt.Println("If ~3s → concurrent | If ~6s → sequential ")
}
// The output for this code :
[1080p] started cooking
[1080p] done!
[720p] started cooking
[720p] done!
[480p] started cooking
[480p] done!
⏱ Total time: 6s
If ~3s → concurrent | If ~6s → sequential`
This above code is running sequentially.
Write on Medium
Now, lets see the optimized way of doing it using go:
`package main
import (
"fmt"
"sync"
"time"
)
func cook(dish string, seconds int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("[%s] started cooking\n", dish)
time.Sleep(time.Duration(seconds) * time.Second)
fmt.Printf("[%s] done!\n", dish)
}
func main() {
var wg sync.WaitGroup
start := time.Now()
wg.Add(3)
go cook("1080p", 3, &wg)
go cook("720p", 2, &wg)
go cook("480p", 1, &wg)
wg.Wait()
fmt.Printf("\n⏱ Total time: %v\n", time.Since(start).Round(time.Second))
fmt.Println("If ~3s → concurrent | If ~6s → sequential ")
}
//output:
[480p] started cooking
[1080p] started cooking
[720p] started cooking
[480p] done!
[720p] done!
[1080p] done!
⏱ Total time: 3s
If ~3s → concurrent | If ~6s → sequential`
So now with this simple example we came to know how to use go and when to use go.
Now this above technique , I have used in transcoding video:
`func HLSTranscode(videoUploadId int64, inputKey string, userId int64) error {
// check if already exists
exists, err := connect.Db.NewSelect().
Model((*models.VideoQuality)(nil)).
Where("video_upload_id = ?", videoUploadId).
Exists(context.Background())
if err != nil {
return fmt.Errorf("failed to check if video exists: %w", err)
}
if exists {
return nil
}
// download from S3
inputFile, err := DownloadFromS3(inputKey)
if err != nil {
return fmt.Errorf("error while downloading from s3: %w", err)
}
defer os.Remove(inputFile)
var qualities = []Quality{
{Name: "1080p", Resolution: "1920x1080", Bitrate: "4000k", AudioRate: "192k"},
{Name: "720p", Resolution: "1280x720", Bitrate: "2500k", AudioRate: "128k"},
{Name: "480p", Resolution: "854x480", Bitrate: "1000k", AudioRate: "96k"},
}
// semaphore — max 3 qualities running in parallel
semaphore := make(chan struct{}, 3)
// waitgroup — wait for ALL qualities to finish
var wg sync.WaitGroup
// collect errors from goroutines
errChan := make(chan error, len(qualities))
for _, q := range qualities {
wg.Add(1)
// capture loop variable — critical in Go
q := q
go func() {
defer wg.Done()
// acquire semaphore slot
semaphore <- struct{}{}
defer func() { <-semaphore }() // release when done
fmt.Printf("started transcoding: %s\n", q.Name)
err := transcodeQuality(inputFile, videoUploadId, userId, q)
if err != nil {
fmt.Printf("failed transcoding %s: %v\n", q.Name, err)
errChan <- fmt.Errorf("quality %s failed: %w", q.Name, err)
return
}
fmt.Printf("✅ finished transcoding: %s\n", q.Name)
}()
}
// wait for all goroutines to finish
wg.Wait()
close(errChan)
// collect any errors
var errs []string
for err := range errChan {
if err != nil {
errs = append(errs, err.Error())
}
}
if len(errs) > 0 {
return fmt.Errorf("transcoding errors: %s", strings.Join(errs, ", "))
}
fmt.Println(" all qualities transcoded successfully")
return nil
}`
Here the role of Semaphore is : Limit the number the goroutines running concurrently.
Follow in github:
https://github.com/astrospkc
Top comments (0)