[Go] go基础4

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.WaitGroup

func main() {
    
    
	// 2.开启一个协程计算器+1
	wait.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.协程执行完毕,计数器-1
	wait.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 := <-ch1
	fmt.Println("v1", v1)
	// 4. 空channel且没有关闭 取值会报错
}
	结果:
	v1 10

2.3 channel 循环取值

func main() {
    
    
	ch1 := make(chan int, 5)
	ch1 <- 1
	ch1 <- 2
	ch1 <- 3
	ch1 <- 4
	ch1 <- 5
	close(ch1)
	for i := range ch1 {
    
    
		fmt.Println(i)
	}
}
结果:
1
2
3
4
5

如果没有close就会报错

fatal error: all goroutines are asleep - deadlock!

2.4 select 多路复用

  1. select说明

    • 传统的方法遍历管道时,如果不关闭会阻塞而导致deadlock,在实际开发中,我们不能确定具体什么时间该关闭管道.
    • 这种方式可以实现从多个管道接收值的需求,但运行性能会差很多
    • 为了应对这种场景,Go内置了select关键字,可以同时响应多个管道的操作.
    • select使用类似switch语句,他有一系列case分支和一个默认的分支.
    • 每个case会对应一个管道的通信(接收和发送)过程.
    • select会一直等待,直到某个case的通信操作完成时,就会执行case分支对应的语句.
    func main() {
          
          
    	// 1. 定义channel
    	ch1 := 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 int
    
    func 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.WaitGroup
    
    func 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.Mutex
    
    func 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位十进制数字,不够就高位补0
	Today := 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 variables
	var name string
	var address string
	flag.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 variables
	var name string
	var address string
	flag.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 返回json
	type Info struct {
    
    
		Name     string
		Password string
		Age      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传入的值

  1. 通过iouttil.ReadAll 读出http.Request.body的结构体
  2. 通过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 转结构体
	// 定义个一个格式一样的struct
	var d Info2
	json.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方法

  1. 通过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 Info
	json.Unmarshal(body, &b)
	fmt.Println(b.Name)
}

type Info struct {
    
    
	Name string `json:"name"`
}
结果
{
    
    "Name":"zhangsan","Password":"123456","Age":18}

zhangsan
  1. 通过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 Info
	json.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")
}

猜你喜欢

转载自blog.csdn.net/qq_29974229/article/details/128240808
go