1. 并发编程
1.1 并发和并行
并发: 多个线程在同个核心的CPU上运行.并发的本质是串行.
并行: 多个线程在多个核心的CPU上运行.
1.2 协程和线程
协程: 独立的栈空间,共享堆空间,调度由用户控制,本质上有点类似用户及线程,这些用户及线程的调度也是自己实现的.
线程: 一个线程上可以跑多个协程,协程是轻量级的线程.(操作系统调度的)
1.3 goroutine
Go 语言中goroutine就是一种机制,类似于线程,但它是由Go运行时(runtime)调度和管理
Go程序会智能地将goroutine中的任务合理分配给每个CPU
Go语言之所以被称为现代化的编程语言,就是因为它在语言层面已经内置了调度和上下文切换机制.
在Go语言编程中你不需要自己去写进程,线程,协程,你的技能包里只要有一个goroutine就可以.
当你需要让某个任务并发执行时,只需要把这个任务包装成一个函数.
开启一个goroutine去执行这个函数就可以了,就是这么简单粗暴.
1.4 协程的使用
正常情况下
func main() {test()
}func test() {for i := 0; i < 10; i++ {fmt.Println(i)}
}
结果
0
1
2
3
4
5
6
7
8
9
开启协程方法1:
go 方法名()
go test()
可以看到这里main和test是一起打印的.
func main() {go test()for i := 0; i < 10; i++ {fmt.Println("main", i)time.Sleep(time.Microsecond * 100)}time.Sleep(time.Second)fmt.Println("done")
}func test() {for i := 0; i < 10; i++ {fmt.Println("test", i)time.Sleep(time.Microsecond * 100)}
}
结构:
main 0
test 0
test 1
main 1
main 2
test 2
main 3
test 3
main 4
test 4
main 5
test 5
test 6
main 6
main 7
test 7
test 8
main 8
main 9
test 9
done
1.4.2 sync.WaitGroup
线程开启时候协程
goroutine 开启wait.add(1) 计数器加1
goroutine结束wait.Done()计数器减1
groutine退出wait.wait()判断当前grouproutine是否为0,为0就退出.
// 1. 定义计数器
var wait sync.WaitGroupfunc main() {// 2.开启一个协程计算器+1wait.Add(1)go test()// 4.计算器为0时退出wait.Wait()fmt.Println("Done!")
}
func test() {for i := 0; i < 10; i++ {fmt.Println("main", i)time.Sleep(time.Microsecond * 100)}// 3.协程执行完毕,计数器-1wait.Done()
}
2. channel
2.1 Channel说明
-
共享内存交互数据弊端
- 单纯地将函数并发执行是没有意义的,函数与函数间需要交换数据才能体现并执行函数的意义.
- 虽然可以使用共享内存进行数据交互,但是共享内存在不同的goroutine中容易发生竞态问题.
- 为了保证数据交换的正确性,必须使用互斥量对内存进行加锁,这种做法势必造成性能问题.
-
channel好处
- Go语言中的通道(channel)是一种特殊的类型.
- 通道像一个传送带或者队列,总是遵循先进先出规则,保证收发数据的顺序.
- 每一个通道都是一个具体类型的管道,也就是声明channel的时候需要为其指定元素类型.
- goroutine并发执行时,channel就是他们之间的连接
- channel是让一个goroutine发送特定的值到另一个goroutine的通讯机制
2.2 channel类型
var 变量 chan 元素类型
var ch1 chan int //整形管道
var ch2 chan bool //布尔型
var cha3 chan []int //切片管道
func main() {// 1. 定义channel// make 可以给切片,map,channel分配内存// chan 关键字,int channel类型,5 channel的长度大小,就是最多可以往ch1里存多少个数据,如果存第6个就会出错.ch1 := make(chan int, 5)// 2. 向channel存入数据ch1 <- 10// 3. 从channel取数据v1 := <-ch1fmt.Println("v1", v1)// 4. 空channel且没有关闭 取值会报错
}结果:v1 10
2.3 channel 循环取值
func main() {ch1 := make(chan int, 5)ch1 <- 1ch1 <- 2ch1 <- 3ch1 <- 4ch1 <- 5close(ch1)for i := range ch1 {fmt.Println(i)}
}
结果:
1
2
3
4
5
如果没有close就会报错
fatal error: all goroutines are asleep - deadlock!
2.4 select 多路复用
-
select说明
- 传统的方法遍历管道时,如果不关闭会阻塞而导致deadlock,在实际开发中,我们不能确定具体什么时间该关闭管道.
- 这种方式可以实现从多个管道接收值的需求,但运行性能会差很多
- 为了应对这种场景,Go内置了select关键字,可以同时响应多个管道的操作.
- select使用类似switch语句,他有一系列case分支和一个默认的分支.
- 每个case会对应一个管道的通信(接收和发送)过程.
- select会一直等待,直到某个case的通信操作完成时,就会执行case分支对应的语句.
func main() {// 1. 定义channelch1 := make(chan int, 10)for i := 0; i < 10; i++ {ch1 <- i}ch2 := make(chan string, 10)for i := 0; i < 10; i++ {ch2 <- strconv.Itoa(i)}for {select {case v := <-ch1:fmt.Println("int channel", v)time.Sleep(100 * time.Millisecond)case s := <-ch2:fmt.Println("string channel", s)time.Sleep(100 * time.Millisecond)default:fmt.Println("Channel 中数据已经取完.")return}} } 结果 int channel 0 string channel 0 int channel 1 string channel 1 int channel 2 string channel 2 int channel 3 string channel 3 int channel 4 string channel 4 int channel 5 int channel 6 int channel 7 string channel 5 string channel 6 int channel 8 int channel 9 string channel 7 string channel 8 string channel 9 Channel 中数据已经取完.
3. 互斥锁
- 互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个goroutine可以访问共享资源.
- Go语言中使用sync包的Mutex类型来实现互斥锁
var x intfunc main() {fmt.Println(x)add()fmt.Println(x) } func add() {for i := 1; i <= 5000; i++ {x += 1} } 结果 0 5000
当开启了多个协程对一个资源进行操作,就出现了资源竞争.
var x int var wg sync.WaitGroupfunc main() {wg.Add(2)fmt.Println(x)go add()go add()wg.Wait()fmt.Println(x) } func add() {for i := 1; i <= 5000; i++ {x += 1}wg.Done() } 结果 0 7606 第二次运行结果是 0 8813
为了保证数据正常,需要加上互斥锁.
var x int var wg sync.WaitGroup// 1. 定义互斥锁 var lock sync.Mutexfunc main() {wg.Add(2)fmt.Println(x)go add()go add()wg.Wait()fmt.Println(x) } func add() {for i := 1; i <= 5000; i++ {// 2. 执行前加锁lock.Lock()x += 1// 3. 执行完解锁lock.Unlock()}wg.Done() } 结果 0 10000
3. fmt
常用占位符
参数 | 功能 |
---|---|
%v | 按值的本来值输出 |
%+v | 在%v基础上,对结构体字段名和值进行展开 |
%#v | 输出go语言语法格式的值 |
%T | 类型的值 |
%% | 输出%%本体 |
%b | 以二进制显示 |
%o | 以8进制显示 |
%d | 以10进制显示 |
%x | 以16进制显示 |
%X | 以16进制显示,字母大写 |
%U | Unicode字符 |
%f | 浮点数 |
%p | 指针,16进制方式显示 |
3.1 Sprint
将格式化的数据复制给其他变量
s := fmt.Sprintf("姓名: %s age: %d","张三",24)
fmt.Println(s)
结果
姓名: 张三 age: 24
fmt.Printf 不换行
fmt.Println 换行
4. 时间
4.1 时间转换
- 时间对象, golang中定义的一个对象
- time.Now()
- 时间戳: 秒整数形式,1970年1月1日开始
- now.Unix()
- 格式化时间:人看
- now.Format(“2006-01-02 15:04:05”)
func main() {// 1. 获取时间对象now := time.Now()fmt.Printf("%T %v\n", now, now)// 2. 格式化时间 将时间对象,转换为格式化的时间strTime := now.Format("2006-01-02 15:04:05")fmt.Printf("%T %v\n", strTime, strTime)// 3. 时间戳格式 秒的整数形式ts := now.Unix()fmt.Printf("%T %v\n", ts, ts)// 4. 格式化时间转换成时间对象// 4.1 设置时区loc, _ := time.LoadLocation("Asia/Shanghai")// 4.2 传入时间标记2006-01-02 15:04:05 这个值是不能修改的timeObj, _ := time.ParseInLocation("2006-01-02 15:04:05", strTime, loc)fmt.Println(timeObj.Unix())
}
结果
time.Time 2022-12-05 14:00:40.2687082 +0800 CST m=+0.004188601
string 2022-12-05 14:00:40
int64 1670220040
1670220040
4.2 时间类型
func main() {now := time.Now()year := now.Year()month := now.Month()day := now.Day()hour := now.Hour()minute := now.Minute()second := now.Second()fmt.Printf("%v %v %v %v %v %v", year, month, day, hour, minute, second)
}// %02d 保留2位十进制数字,不够就高位补0Today := fmt.Sprintf("%02d-%d-%02 %d:%d:%d", year, month, day, hour, minute, second)fmt.Printf("%T %v", Today, Today)
结果
2022 December 5 14 10 53
2022-12-05 14:15:11
string
4.3 时间间隔
参数 | 含义 |
---|---|
nanosecond | 纳秒 , 十亿分之一秒 |
Microsecond | 1000*nanosecond微秒,一百万分之一秒 |
Millisecond | 1000*Microsecond毫秒,千分之一秒 |
Second | 1000*Microsecond,秒 |
Minute | 60*second,分 |
Hour | 60*Minute,小时 |
4.3.1 Add方法
func main() {now := time.Now()fmt.Println("现在是:", now)m, _ := time.ParseDuration("-1m")m1 := now.Add(m)fmt.Println("前1分钟是:", m1)
}
结果
现在是: 2022-12-05 14:42:46.8769096 +0800 CST m=+0.003688201
前1分钟是: 2022-12-05 14:41:46.8769096 +0800 CST m=-59.996311799
5. Flag
Go语言内置的flag包实现了命令行参数的解析,flag包使得开发命令行工具更为简单.
func main() {// 1. String variablesvar name stringvar address stringflag.StringVar(&name, "name", "张三", "姓名")flag.StringVar(&address, "address", "上海", "地址")flag.Parse()fmt.Println(flag.Args())
}在命令行下执行--help
PS D:\golang\day3\03.flag> go run main.go --help
Usage of C:\Users\Q\AppData\Local\Temp\go-build1568490171\b001\exe\main.exe:-address string地址 (default "上海")-name string姓名 (default "张三")
命令行传参
&name 变量的指针,传入的数据赋值给他
name 命令行里的key
“张三” 如果不传递张三就作为默认值
“姓名” --help里的提示信息.
func main() {// 1. String variablesvar name stringvar address stringflag.StringVar(&name, "name", "张三", "姓名")flag.StringVar(&address, "address", "上海", "地址")flag.Parse()fmt.Println(name, address)
}
命令行执行,如果不传值,就会用默认值替代
PS D:\golang\day3\03.flag> go run main.go -name "李四" -address "北京"
李四 北京
PS D:\golang\day3\03.flag> go run main.go -name "李四"
李四 上海
fmt.Println(name, address)// Args 可以接收除了name和address以外的传入变量fmt.Println(flag.Args())
结果
PS D:\golang\day3\03.flag> go run .\main.go 1 3 2 4
张三 上海
[1 3 2 4]
6. net-http
它既能提供server端,又能提供client端.
6.1 Get请求
方法名称 | 描述 |
---|---|
Header() | 用户设置或获取响应头信息 |
write() | 用于写入数据响应体 |
WriteHeader() | 用于设置响应状态码,若不调用则默认状态码为200 OK. |
6.1.1 返回数据
启动一个http服务,进行简单的返回一个数据
/*
1. 路由
2. 处理函数1. 解析请求数据2. 处理函数将结果进行返回3. 启动服务
*/
func main() {// 1. 定义路由http.HandleFunc("/req/get", dealGetHandler)fmt.Println("http://127.0.0.1:8080/req/get")// 3. 启动服务// addr: 当前server监听的端口号,handler:处理函数http.ListenAndServe(":8080", nil)}// 2. 定义处理函数,用驼峰命名,以xxxHandler为函数名
// Get 请求
// http.ResponseWriter 返回数据给浏览器的,本质是一个interface接口,定义了三个方法,进行返回数据
// *http.Request 将传过来的参数放入Request结构体中. 解析url中的数据或post请求body的数据
func dealGetHandler(w http.ResponseWriter, r *http.Request) {// 直接返回数据w.Write([]byte("hello world"))
}
6.1.2 解析数据
// 1. 解析请求的数据query := r.URL.Query()fmt.Println(query)
当浏览器输入http://127.0.0.1:8080/req/get?name=zhangsan时,得到以下返回
http://127.0.0.1:8080/req/get?name=zhangsan
6.1.3 通过get取值
func deal2GetHandler(w http.ResponseWriter, r *http.Request) {query := r.URL.Query()name2 := query.Get("name")fmt.Println(name2)// 直接返回数据w.Write([]byte("hello world"))
}
6.1.4 返回json值
func deal2GetHandler(w http.ResponseWriter, r *http.Request) {query := r.URL.Query()name2 := query.Get("name")fmt.Println(name2)// 1.1 直接返回字符串//w.Write([]byte("hello world"))// 1.2 返回jsontype Info struct {Name stringPassword stringAge int64}u := Info{Name: name2,Password: "123456",Age: 18,}json.NewEncoder(w).Encode(u)
}
返回内容:
{"Name":"张三","Password":"123456","Age":18}
6.2 Post请求
func main() {// 1. 定义路由http.HandleFunc("/req/get", deal2GetHandler)http.HandleFunc("/req/post", dealPostHandler)fmt.Println("http://127.0.0.1:8080/req/get")// 3. 启动服务// addr: 当前server监听的端口号,handler:处理函数http.ListenAndServe(":8080", nil)
}
// Get部分省略// 和Get请求一样写法
func dealPostHandler(w http.ResponseWriter, r *http.Request) {// r.URL.query() 从url取参数// post 从http的body取中获取数据bodyContent, _ := ioutil.ReadAll(r.Body)fmt.Printf("%T %v\n", bodyContent, bodyContent)w.Write([]byte("hello Post"))
}
6.2.1 解析POST传入的值
- 通过iouttil.ReadAll 读出http.Request.body的结构体
- 通过json的绑定,将数据绑定到定义的结构体
func dealPostHandler(w http.ResponseWriter, r *http.Request) {// r.URL.query() 从url取参数// post 从http的body取中获取数据bodyContent, _ := ioutil.ReadAll(r.Body)// uint8 转string//strData := string(bodyContent)// string 转结构体// 定义个一个格式一样的structvar d Info2json.Unmarshal(bodyContent, &d)// 获取到的name的数据fmt.Println(d.Name, d.Password)//fmt.Printf("%T %v\n", bodyContent, strData)w.Write([]byte("hello Post"))
}type Info2 struct {Name string `json:"name"`Password string `json:"password"`
}
结果
zhangsan root123
6.3 请求数据
6.3.1 Get方法
- 通过body进行解析
func main() {apiUrl := fmt.Sprintf("http://127.0.0.1:8080/req/get?name=zhangsan")resp, err := http.Get(apiUrl)if err != nil {fmt.Println(err)return}body, _ := ioutil.ReadAll(resp.Body)fmt.Println(string(body))var b Infojson.Unmarshal(body, &b)fmt.Println(b.Name)
}type Info struct {Name string `json:"name"`
}
结果
{"Name":"zhangsan","Password":"123456","Age":18}zhangsan
- 通过url进行解析
func main() {//从这里开始apiUrl := "http://127.0.0.1:8080/req/get"data := url.Values{}data.Set("name", "zhangsan1")u, _ := url.ParseRequestURI(apiUrl)u.RawQuery = data.Encode()// 到这里其实就是在拼接url// 后面的和之前的一样resp, err := http.Get(u.String())if err != nil {panic(err)}body, _ := ioutil.ReadAll(resp.Body)fmt.Println(string(body))var i Infojson.Unmarshal(body, &i)fmt.Println(i.Name, i.Password, i.Age)
}type Info struct {Name string `json:"name"`Password string `json:"Password"`Age int `json:"Age"`
}
结果:
{"Name":"zhangsan1","Password":"123456","Age":18}zhangsan1 123456 18
6.3.2 Post方法
func main() {url := "http://127.0.0.1:8080/req/post"// 表单数据提交 form submission//contentType := "application/x-www-form-urlencoded"// Json数据提交contentType := "application/json"data := `{"name": "zhangsan","password": "root123"
}`resp, _ := http.Post(url, contentType, strings.NewReader(data))b, _ := ioutil.ReadAll(resp.Body)fmt.Println(string(b))
}
结果:
hello Post
服务器端结果:
zhangsan root123
7. Os模块
func main() {// 1. 获取当前目录fmt.Println(os.Getwd())// 2. 切换路径os.Chdir("d:\\game\\")fmt.Println(os.Getwd())// 3. 创建文件夹os.Mkdir("test", 0777)// 4. 删除//os.Remove("test")// 5. 重命名os.Rename("test", "test2")// 6. 新建文件os.Create("test2/test.txt")
}