Go Basic|青训营

120 阅读2分钟

Playlist:

Scanf vs bufio

Source code: guessing-game/v5

The source file uses the following code block to handle the inputstream and type convertion:

reader := bufio.NewReader(os.Stdin)
for {
	input, err := reader.ReadString('\n')
	if err != nil {
		fmt.Println("An error occured while reading input. Please try again", err)
		continue
	}
	input = strings.Trim(input, "\r\n")
	guess, err := strconv.Atoi(input)
	if err != nil {
		fmt.Println("Invalid input. Please enter an integer value")
		continue
	}
	...
}

However, in this case, we can use fmt.Scanf as a shorthand. It implictly helps you find and read the input intger into the guess, omit non-integer inputs and meanless linebreaks.

var guess int
for {
	fmt.Scanf("%d", &guess)
	...
}

When should we use bufio:

  • Use bufio when you want to read or write data more efficiently, especially in cases where you are reading or writing data in small chunks. The buffering provided by bufio can significantly reduce the number of system calls, leading to better performance.
  • Use bufio.Reader when you want to read data line by line from an input source, especially when processing large text files where reading line by line is more memory-friendly.
  • Use bufio.Writer when you need to write data in smaller chunks, and you want to take advantage of buffering to reduce the number of writes.

Goroutine

Go program is a threaded program. You can have multiple threads owning seperate PCs, seperate sets of registers and seperate stacks in one single execution. All things in go including the func main() are goroutine s. Though goroutine offers great convinience to write serial codes for threaded program, we still need to take care of some implict challenges like data sharing (data race, use flag -race), coordination (channels, sync.Cond, sync.WaitGroup) and deadlock.

Running a single go program creates a new process in the machine and gets its own memory address. Each thread (goroutine) created by the process shares it.

Web Crawler

Playground: Ex: Web Crawler

Avoid race

type concurrentMap struct {
	mu		sync.Mutex
	fetched map[string]bool
}

var cm concurrentMap

// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher) {
	if depth <= 0 {
		return
	}
	cm.mu.Lock()
	already := cm.fetched[url]
	cm.fetched[url] = true
	cm.mu.Unlock()
	if already {
		return
	}
	body, urls, err := fetcher.Fetch(url)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("found: %s %q\n", url, body)
	var done sync.WaitGroup
	for _, u := range urls {
		done.Add(1)
		go func(u string) {
			defer done.Done()
			Crawl(u, depth-1, fetcher)
		}(u)
	}
	done.Wait()
	return
}

Coordination

Channels actually has a Mutex object in it to protect concurrent usages for recieving and sending massages. The Mutex object is hidden because go programmer should not care about traditional lock-based synchronization (abstraction). Golang encourages a communication-based approach using goroutines and channels to win concurrent efficiency with little overhead.

func worker(url string, ch chan []string, fetcher Fetcher) {
	urls, err := fetcher.Fetch(url)
	if err != nil {
		ch <- []string{}
	} else {
		ch <- urls
	}
}

func master(ch chan []string, fetcher Fetcher) {
	n := 1
	fetched = make(map[string]bool)
	for urls := range ch {
		for _, u := range urls {
			if fetched[u] == false {
				feteched[u] == true
				n += 1
				go worker(u, ch, Fetcher)
			}
		}
		n -= 1
		if n == 0 {
			break
		}
	}
}

func main() {
	ch := make(chan []string)
	go func() {
		ch <- []string{url}
	}
	master(ch, Fetcher)
}

Simple Dict

Source code: simpledict/v4

Spec: fires up a new request to another online translator and works concurrently with the current one for better effieciency.

var done sync.WaitGroup
done.Add(1)
go func(w string) {
	defer done.Done()
	query(w, 0)
}(word)
go func(w string) {
	defer done.Done()
	query(w, 1)
}(word)
done.Wait()

Flaws. Occasionally, the fmt.Println() prints ahead. It is better to return the fmt.Sprint()object and print the reponse in func main().

➜  go-by-example git:(master) ✗ go run simpledict/v4/main.go hello
1        hello UK: [ˈheˈləu] US: [həˈlo]
int.喂;哈罗
n.引人注意的呼声
v.向人呼(喂)
0        hello UK: [ˈheˈləu] US: [həˈlo]
int.喂;哈罗
n.引人注意的呼声

Go to project

Go Module

Document: Using Go Modules

  1. Initializing Go Modules: You can use the go mod init command to initialize a new Go module in your project. The command should be executed in the root directory of your project, and it creates the go.mod file that will track the project's dependencies and versions.
go mod init path/to/root

Replace path/to/root with the import path you want to use for your project. The import path can be any valid Go package path that represents your project's root.

  1. Listing Dependencies: The go list -m all command allows you to list all the direct dependencies of your Go module along with their versions. This helps you see the current dependency tree of your project.
go list -m all
  1. Tidying Dependencies: The go mod tidy command is used to clean up the go.mod file by removing any dependencies that are no longer imported by your code. It also ensures that the dependencies and their versions are consistent.
go mod tidy
  1. Downloading and Installing Dependencies: The go get command is used to download and install the dependencies listed in the go.mod file. It fetches the specified versions of the modules and places them in the appropriate location within your Go workspace.
go get ./...
  1. Downloading Dependencies without Installing: The go mod download command only downloads the dependencies without installing them. It fetches the modules and saves them to the Go module cache but does not build or install the packages.
go mod download

Additional Notes:

  • When you use the go get or go mod download commands, Go modules will automatically update the go.sum file, which contains checksums of the downloaded modules. This ensures the security and integrity of the dependencies.

  • The go.mod file serves as the single source of truth for your project's dependencies. It is automatically updated when you run certain commands, like go get or go mod tidy. However, it's essential to review and commit changes to the go.mod file to version control when working collaboratively on a project.

  • If you ever need to manually edit the go.mod file, make sure to follow the guidelines for Go modules, including specifying correct versions, using semantic versioning, and avoiding direct editing of the requirements section.

  • Keep in mind that Go modules are designed to work outside of the traditional GOPATH workspace, which allows for more flexibility in managing dependencies and versioning.

Go Test

Unit Test

/* In *_test.go */
import "testing"

func Test*(t *testing.T) {
	/* Add your own tests. */
}

flag -cover shows the coverage.

  • Expectation: 50-60% on avergae, favorable >80%

About the attention s.

func NumUnmarshal(){  
	jsonStr:=`{"id":1,"name":"Jerry"}`  
	var res map[string]interface{}  
	_ = json.Unmarshal([]byte(jsonStr), &res)  // any numeric values will be converted into floating point
	fmt.Printf("%T\n",res["id"])  // print the type of res[id]
	// fmt.Printf("#{res["id"]}") do the same thing
	i := res["id"].(int64)  // try to assert type of i is int64 (false)
	
	/* you can set decoder to make it work as properly */
	//decoder := json.NewDecoder(bytes.NewReader([]byte(jsonStr)))  
	//decoder.UseNumber()  
	//_ = decoder.Decode(&res)  
	//i, _ := res["id"].(json.Number).Int64()

	/* In most time, we use a struct to hold the bytes */
	//type Result struct { 
	//	ID int64 `json:"id"` 
	//  Name string `json:"name"` 
	//}

	fmt.Println(i)  
}

Mock Test

todo.

Benchmark

todo.

Go Practice

Typical layers of abstraction for a project design

clean-arch.png

  1. 支持对话题发布回帖。
  2. 回帖id生成需要保证不重复、唯一性。
  3. 新加回帖追加到本地文件,同时需要更新索引,注意Map的并发安全问题 。