Skip to content

Instantly share code, notes, and snippets.

@BruceChen7
Last active June 30, 2021 02:20
Show Gist options
  • Save BruceChen7/ece3bd4b071b7c8c2fd5ff467716bddc to your computer and use it in GitHub Desktop.
Save BruceChen7/ece3bd4b071b7c8c2fd5ff467716bddc to your computer and use it in GitHub Desktop.
[#pakcage#golang] #golang#init#mod#package#concurrency

在golang中的每个模块可以,定义init函数,用来初始化该包内的全局变量,我们可以看看它的特点

package main
import "fmt"
func init() {
    fmt.Println("init 1")
}
func init() {
    fmt.Println("init 2")
}
func main() {
    fmt.Println("main")  // 输出 init 1, init2  main
}

golang 语言

Go has many features. Some are unique, some are borrowed from other programming languages:

  • built-in concurrent programming support
    • goroutines (green threads) and start new goroutines easily.
    • channels (based on CSP model) and select mechanisms to do synchronizations
  • between goroutines.
  • the container types map and slice are first-class citizens.
  • polymorphism through interfaces.
  • value boxing and reflection through interfaces.
  • pointers.
  • function closures.
  • methods.
  • deferred function calls.
  • type embedding.
  • type deduction.
  • memory safety.
  • automatic garbage collection.
  • great cross-platform compatibility.

concurrency

sync.Mutex使用错误

package main

import (
  "fmt"
  "sync"
  "time"
)

type Container struct {
  sync.Mutex                       // <-- Added a mutex
  counters map[string]int
}

func (c Container) inc(name string) {
  c.Lock()                         // <-- Added locking of the mutex
  defer c.Unlock()
  c.counters[name]++
}

func main() {
  c := Container{counters: map[string]int{"a": 0, "b": 0}}

  doIncrement := func(name string, n int) {
    for i := 0; i < n; i++ {
      c.inc(name)
    }
  }

  go doIncrement("a", 100000)
  go doIncrement("a", 100000)

  // Wait a bit for the goroutines to finish
  time.Sleep(300 * time.Millisecond)
  fmt.Println(c.counters)
}

输出:

fatal error: concurrent map writes

goroutine 5 [running]:
runtime.throw(0x4b765b, 0x15)

<...> more goroutine stacks
exit status 2

原因是什么,因为inc的receiver是值类型,而不是指针,而Mutex是值类型,所以会拷贝mutex,而map是引用类型,所以mutex保护不了map。证明:

package main

import "fmt"

type Container struct {
  i int
  s string
}

func (c Container) byValMethod() {
  fmt.Printf("byValMethod got &c=%p, &(c.s)=%p\n", &c, &(c.s))
}

func (c *Container) byPtrMethod() {
  fmt.Printf("byPtrMethod got &c=%p, &(c.s)=%p\n", c, &(c.s))
}

func main() {
  var c Container
  fmt.Printf("in main &c=%p, &(c.s)=%p\n", &c, &(c.s))

  c.byValMethod()
  c.byPtrMethod()
}

输出

in main &c=0xc00000a060, &(c.s)=0xc00000a068
byValMethod got &c=0xc00000a080, &(c.s)=0xc00000a088
byPtrMethod got &c=0xc00000a060, &(c.s)=0xc00000a068

资料

repository and module and package

  • A repository contains one or more Go modules.
  • Each module contains one or more Go packages.
  • Each package consists of one or more Go source files in a single directory

常见模块工作命令:

  • go mod init creates a new module, initializing the go.mod file that describes it.
  • go build, go test, and other package-building commands add new dependencies to go.mod as needed.
  • go list -m all prints the current module’s dependencies.
  • go get changes the required version of a dependency (or adds a new dependency).
  • go mod tidy removes unused dependencies.

go.mod

go.mod支持4种指令

  • require
  • replace
  • exclude
  • module
module github.com/my/thing

require (
    github.com/some/dependency v1.2.3
    github.com/another/dependency/v4 v4.0.0
)

module指令提供module path,在一个模块中的所有package都是基于这个路径前缀。

The module path and the relative path from the go.mod to a package's directory together determine a package's import path.

  • exclude and replace 作用于current("main") module

例子

go mod开始

# GOPATH之外创建一个目录
$ mkdir -p /tmp/scratchpad/hello
$ cd /tmp/scratchpad/hello
# 初始化一个新的模块
$ go mod init github.com/you/hello #该项目代码的引用路径是github.com/you/hello

go mod init <ROOTPATH>,ROOTPATH 是项目的 import 路径,引用该项目中的 package 时使用前缀github.com/you/hello。开始写代码

package main

import (
    "fmt"
    "rsc.io/quote"
)

func main() {
    fmt.Println(quote.Hello())
}

开始编译:

$ go build 
$ ./hello

Hello, world.

这时,go.mod将会更新依赖信息

$ cat go.mod

module github.com/you/hello
require rsc.io/quote v1.5.2

注意上面没有使用go get 命令。

如果被外部项目引用,go.mod 中设置的 package 路径需要与代码的获取地址相同,项目内部引用没有该限制,github.com/introclass/go-mod-example 的 go.mod 中标注的是 example.com/hello,代码获取地址 github.com/intraoclass/go-mode-example 与 example.com/hello 不一致,在另一个项目中用 github 地址加载时会失败

依赖包的存放位置

依赖包既不在 $GOPATH/src 目录中,也不在 vendor 目录,而在$GOPATH/pkg/mod中。

$ ls $GOPATH/pkg/mod/github.com/lijiaocn/
codes-go@v0.0.0-20180220071929-9290fe35de7e golib@v0.0.1

$ ls $GOPATH/pkg/mod/github.com/lijiaocn/golib@v0.0.1
config container generator terminal version virtio

删除未使用的依赖

$ go mod tidy
$ go list -m all
example.com/hello
github.com/lijiaocn/golib v0.0.2

日常工作流

  • import你依赖的库
  • 使用标准命令go build 和 go test
  • 当你需要使用某个版本的依赖,可以直接编辑go.mod

一种组织项目的方式

目标:

  • 将模块组织成多个package,每个package可以被别的用户来使用
  • Internal packages: 只被这个模块所引用。
  • commands/program

结构

├── LICENSE
├── README.md
├── config.go
├── go.mod
├── go.sum
├── clientlib
│   ├── lib.go
│   └── lib_test.go
├── cmd
│   ├── modlib-client
│   │   └── main.go
│   └── modlib-server
│       └── main.go
├── internal
│   └── auth
│       ├── auth.go
│       └── auth_test.go
└── serverlib
    └── lib.go
  • go.modis the module definition file. It contains the module name shown above and that's it
  • go.sum contains all the dependency checksums, and is managed by the go tools. keep it checked into source control alongside go.mod.
  • config.go
package modlib

func Config() string {
  return "modlib config"
}

这个代码中最重要的事情就是package modlib,因为这个文件在这个模块的顶层,所以其package name应该是模块的名称。这样应用侧的代码是:

package main 

import "fmt"
package main

import "fmt"
import "github.com/eliben/modlib"

func main() {
  fmt.Println(modlib.Config())
}

所以这里的规则很简单,如果你的模块只有一个包,那么你可以将所有的代码都放在你的模块的顶层目录,这样,包名就是你定义模块路径的最后一个部分。

  • clientlib/lib.go
package clientlib

func Hello() string {
  return "clientlib hello"
}

lib.go是clientlibpackage的一个文件。这个文件的名称是什么,不重要,重要的是package clientlib声明了一个clientlib package。引用clientlib

package main

import "fmt"
import "github.com/eliben/modlib"
import "github.com/eliben/modlib/clientlib"

func main() {
  fmt.Println(modlib.Config())
  fmt.Println(clientlib.Hello())
}

commands/programs 如果你的项目是一个可执行的program或者是comamnds,那么我们可以创建一个cmd目录,这样我们可以这样使用go get命令

$ go get github.com/eliben/modlib/cmd/cmd-name

# Go downloads, builds and installs cmd-name into the default location.
# The bin/ directory in the default location is often in $PATH, so we can
# just invoke cmd-name now

$ cmd-name ...

在这里重要的是,program或者是command的文件应该使用package main。这样我呢么可以这样使用:

$ go get github.com/eliben/modlib/cmd/modlib-client
$ modlib-client
Running client
Config: modlib config
clientlib hello

$ go get github.com/eliben/modlib/cmd/modlib-server
$ modlib-server
Running server
Config: modlib config
Auth: thou art authorized
serverlib hello

# Clean up...
$ rm -f `which modlib-server` `which modlib-client`

internal packages 对于内部使用的package,你不想暴露出去成为public APi的一部分,那么你可以在包的定义路径上使用internal,这样当用户使用引用该路径下的代码时,将会出错。

use of internal package github.com/eliben/modlib/internal/auth not allowed

尽可能的将package设置成internal,因为将internal改成public是很简单的,内胆是从public改成internal是很困难。

@BruceChen7
Copy link
Author

go mod的解析

image

@BruceChen7
Copy link
Author

BruceChen7 commented Jun 30, 2021

Go 私有包的构建和使用

https://www.chenshaowen.com/blog/building-and-using-go-private-packages.html

使用私有仓库

git config --global url."git@gitlab.private.com:".insteadof "https://gitlab.private.com/"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment