文章目录
- 一、基础
- 1.DOS命令
- 2.变量
- (1)局部变量
- (2)全局变量
- (3)数据类型
- (4)指针
- (5)运算符
- (6)自定义数据类型
- 3.语句
- (1)条件语句
- (2)循环语句
- 4.函数
- (1)基础
- (2)结构
- 二、常用函数
- 0.基础
- (1)len() 和 cap()
- (2)new()
- (3)make(Type, len, cap)
- (4)nil
- (5)append() 和 copy()
- (6)Range关键字
- (7)_
- (8)element()
- (9).(type)
- 1.package unsafe
- (1)unsafe.Sizeof(num)
- 2.import "fmt"
- (1)输出
- (2)输入
- 3.import "strconv"
- 4.字符串函数
- 5.import "os"
- 6.import "io"
- 7.import "io/ioutil"
- 8.import "bufio"
- 9.import "sync"
- 三、包及其函数
- 1.包的引入
- 2.init函数
- 3.匿名函数与闭包
- 4.defer关键字
- 5.日期和时间函数
- 6.错误处理
- (1)错误捕获
- (2)自定义错误
- 四、数组、切片、映射
- 1.数组
- 2.切片 []
- 3.集合/字典/映射 Map
- 五、面向对象
- 1.结构体
- (1)基础
- (2)转换
- (3)方法的引入
- (4)方法与函数的差别
- (5)创建结构体实例
- 2.封装
- 3.继承
- (1)基础
- (2)注意事项
- 4.接口
- (1)基础
- (2)注意事项
- (3)多态
- 6.断言
- 六、文件操作
- 1.文件
- 2.io读取文件
- 3.io写入文件
- 4.文件复制操作
- 七、协程和管道
- 1.基本概念
- (1)程序
- (2)进程
- (3)线程
- (4)协程
- 2.协程
- (1)多协程
- (2)延长协程存活
- (3)互斥锁同步协程
- (4)读写锁
- 3.管道
- (1)管道关闭
- (2)管道遍历
- (3)协程与管道
- (4)只读只写管道的声明
- (5)管道阻塞
- 4.select功能
- 5.defer+recover
- 八、网络编程
- 1.创建客户端
- 2.创建服务器
- 3.连接测试
- 4.发送终端数据
- 九、反射
一、基础
1.DOS命令
D: //转d盘
go build helloworld.go //编译.exe
go run helloworld.go //编译并执行.exe
// /**/ 注释
2.变量
(1)局部变量
var a int //声明变量
a = 10 //赋值
var b = 20 //自动类型推断
c := "qwq" //省略var
var d,e,f = 10 , "jack" , 3.14 //多变量声明,:=等也可
(2)全局变量
var (
n1=1
n2=2
) //多个声明
(3)数据类型
基本数据类型(数值型):
int , int8 , int16 , int32 , int64 ,
uint, uint8, uint16 , uint32 , uint64 , byte ,
float32 , float64 //可以用科学计数法如 314E-2
基本数据类型(字符型):无,使用
byte
基本数据类型(布尔型):
bool
基本数据类型(字符串):
string
"qwq" or 'q'//无特殊类型采用
`qwq`//有特殊类型采用
//+号拼接
复杂数据类型:指针、数组、结构体、管道、函数、切片、接口、map
类型转换(显式转换):同C++
(4)指针
&num //地址
var ptr *int = &num //指针定义
*ptr //获取地址对应的值,很像C++
(5)运算符
(6)自定义数据类型
相当于起别名,但是编译器不会认为起名前后是一种数据类型
type myInt int
type
3.语句
(1)条件语句
类似C++
// if
if 布尔表达式 1 {
/* 在布尔表达式 1 为 true 时执行 */
}
else if 布尔表达式2{
}
else{
}
// switch,注意:自带break
switch var1 {
case val1:
...
case val2:
...
default:
...
}
// select
(2)循环语句
// 格式
for init; condition; post {
}
for condition {
}
// range示例
for key, value := range oldMap {
newMap[key] = value
}
//无限循环
for true{
}
for ;;{
}
for {
}
break //类似
break label1 //将指定label的循环终止(可以是大循环)
continue //同C++
return
4.函数
(1)基础
// 格式,函数名首字母大写,可以被本包和其他包文件使用;首字母小写,只能被本包使用
func function_name( [parameter list] ) [return_types] {
函数体
}
// 示例,不返回值时,[return_types]是可以被省略的
func swap(x, y string) (string, string) {
return y, x
}
func swap(x, y string) (y string, x string) {
return
}
注意:go不支持函数重载
// 可变参数,可以传入任意个变量,传入变量当做切片来处理
func test(args...int){
}
// 地址传递,同C++
test(&num)
func test(num int*){
*num = 10
}
函数也是一种数据类型,可以赋值给变量,通过该变量可以实现函数调用,函数可以做为形参。
a := test
a(10) //调用函数
// 函数可以作为形参
fun test2(testFunc func(int)){
}
(2)结构
说明:
func (目标形参名 绑定目标) 函数名(传入形参名 形参数据类型, 传入形参名 形参数据类型) ([返回的变量] 返回数据类型){
}
/* 常规函数 */
func 函数名(传入形参名 形参数据类型, 传入形参名 形参数据类型) ([返回的变量] 返回数据类型){
}
func 函数名(传入形参名 形参数据类型, 传入形参名 形参数据类型) {
}
/* 工厂模式 */
func 首字母大写函数名(传入形参名 形参数据类型, 传入形参名 形参数据类型) *结构体名{
}
/* 方法 */
func (目标形参名 绑定目标) 函数名(传入形参名 形参数据类型, 传入形参名 形参数据类型) ([返回的变量] 返回数据类型){
} // 值传递
func (目标形参名 *绑定目标) 函数名(传入形参名 形参数据类型, 传入形参名 形参数据类型) ([返回的变量] 返回数据类型){
} // 指针传递
示例:
// 定义一个后续需要的结构体
type person struct{
Name string
age int
}
/* 常规函数 */
func GetPerson(Name string, Age int ,Year int) (NewYear int){
fmt.Println("正在计算" + Name + "现在的年龄")
NewYear = Age + Year
return
}
func GetPerson(Name string, Age int ,Year int) (int){
fmt.Println("正在计算" + Name + "现在的年龄")
NewYear = Age + Year
return NewYear
}
func GetPerson(Name string) {
fmt.Println("正在计算" + Name + "现在的年龄")
}
/* 工厂模式 */
func NewPerson(name string) *person{
return &person{
Name : name
}
}
/* 方法 */
//与常规函数类似,只是多了一个绑定
func (p person) GetPerson(传入形参名 形参数据类型, 传入形参名 形参数据类型) ([返回的变量] 返回数据类型){
} // 值传递
func (p *person) 函数名(传入形参名 形参数据类型, 传入形参名 形参数据类型) ([返回的变量] 返回数据类型){
} // 指针传递,函数调用和结构体属性调用无需&或*
二、常用函数
0.基础
(1)len() 和 cap()
len同python,cap可以提供切片最长长度
(2)new()
num := new(int) // new的实参是一个类型,返回值是对应类型的指针
(3)make(Type, len, cap)
内存分配的内置函数:数据类型(slice,map,channel),长度(对于slice是必选),提前预留长度(可选)
(4)nil
判断数组时表示空
(5)append() 和 copy()
切片的追加和复制
num2 = append(num, 0) // 追加切片
num = copy(num2, num) // 把num的内容复制到num2,往往用于增加切片容量
(6)Range关键字
用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。
for key, value := range oldMap {
newMap[key] = value
}
for key := range oldMap {
newMap[key] = value
}
for key, _ := range oldMap {
newMap[key] = value
}
(7)_
忽略作用
(8)element()
value, ok := element.(T)
value为变量的值
ok为bool类型
element是interface变量
T是断言类型
(9).(type)
type关键字,数据类型判断,与switch配合使用:
// 实现数据类型的判断
func checkType(i interface{}) string {
switch i.(type) {
case string:
return "string"
case int:
return "int"
case float64:
return "float64"
default:
return "unknown"
}
}
1.package unsafe
(1)unsafe.Sizeof(num)
返回类型尺寸
2.import “fmt”
(1)输出
Printf("it is %T",num) //格式化输出
Println(num) //直接输出,相邻参数间添加空格
Print("num") //直接输出,相邻非字符串间添加空格
str = Sprintf("it is %T",num) //可以保存字符串
var num2 string = fmt.Sprintf("qwq %d",num) //将num转为string
(2)输入
Scanln(&num) //获取输入,必须是地址,类型需要匹配
Scanf("%d %s" , &age , &name) //获取输入,多个输入,输入需要空格相间
3.import “strconv”
注意:也可用Sprintf()实现同样功能
注意:无效值会被转为默认值
4.字符串函数
strconv 和 strings
rune 类似 int32,以此方式遍历更兼容汉字
5.import “os”
6.import “io”
7.import “io/ioutil”
8.import “bufio”
9.import “sync”
三、包及其函数
1.包的引入
建议包(package)的声明和文件夹名一致,函数首字母必须大写。同一目录下包名必须一致
import "文件夹路径" //import引入
import ( //多个包导入
"fmt"
test "gocode/testproject01/demo01" //起别名,用于调用
)
包名.函数名 //调用
2.init函数
优先级:全局变量>其他包的init>本包的init>main,用法和main差不多
3.匿名函数与闭包
只被用一次的函数,也可以定义为全局的
闭包,对内存消耗大:
func getSum() func (int) int {
var sum int = 10
return func (num int) int{ //匿名函数,该函数引用的变量会一直保存在内存中,可以一直使用
sum = sum + num
return sum
}
}
// 闭包:返回的匿名函数+匿名函数以外的变量sum
func main(){
f := getSum()
fmt.Println(f(1)) //11 传参到匿名函数
}
4.defer关键字
把defer后的语句压入栈中
defer Println("qwq") //函数截止时执行并释放
5.日期和时间函数
import "time"
now := time.Now() //获取当前时间,返回一个结构体
详情见 https://studygolang.com/pkgdoc
6.错误处理
(1)错误捕获
defer func(){ //捕获错误,使得程序可以继续
err := recover()
if err != nil{
...
}
}() //加括号用于调用
(2)自定义错误
import "errors"
err := errors.New("报错...") //报错,并继续运行
panic(err) //报错,并终止运行
四、数组、切片、映射
1.数组
数组声明
// 一维数组
var numbers = [5]int{1, 2, 3, 4, 5}
numbers := [5]int{1, 2, 3, 4, 5}
var numbers = [...]int{1, 2, 3, 4, 5}
numbers := [...]int{1:20, 4:99, 3:25}
// 多维数组
var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type //格式
var arr = [2][3]int{{1,4,7},{2,5,8}}
var arr [2][3]int = [2][3]int{{1,4,7},{2,5,8}}
形参数组
func myFunction(param []int) { //未设定数组大小
....
}
func myFunction(param [10]int) { //固定数组大小
....
}
2.切片 []
var s = []int{1,2,3,4 } //声明方式
s :=[] int {1,2,3 }
var s[]type = make([]type, len) //采用make的声明方式
s := make([]type, len)
arr = s[startIndex:endIndex] // 类似python,包含左不包含右
切片的切片发生变动时,原切片也会发生改变;
参见append() 和 copy()
3.集合/字典/映射 Map
// 使用 make 函数
m := make(map[KeyType]ValueType, initialCapacity)
// 使用字面量创建 Map
m := map[string]int{
"apple": 1,
"banana": 2,
"orange": 3,
}
// 删除键值对
delete(m, "banana")
//查询相关映射是否存在
value, flag := m[orange]
五、面向对象
1.结构体
(1)基础
类似C++,但是golang没有类(class),无重载、构造函数、析构函数、隐藏的this指针等;
但仍有继承、封装、多态等特性
type Books struct {
title string
author string
subject string
book_id int
}
variable_name := Books {value1, value2...valuen} //声明变量
或
variable_name := Books { key1: value1, key2: value2..., keyn: valuen}
结构体.成员名 //访问方式
对于结构体指针,直接指针名加“.”来调用
struct_pointer.title
有指针的简化赋值,特别是在“方法”中,
&
t
.
属性
\&t.属性
&t.属性和
∗
t
.
属性
*t.属性
∗t.属性 都可以简写为
t
.
属性
t.属性
t.属性
(2)转换
两个结构体的名字、个数和类型必须一致,需要运用强制类型转换。
(3)方法的引入
作用在指定数据类型,不仅是适用于struct的。可以为结构体等设置专用函数
type Person struct{
Name string
}
func (p Person) test(){ //方法的绑定
fmt.Println(p.Name)
}
func main(){
var p Person
p.Name = "丽丽"
p.test()
}
//其他类型的绑定
type integer int
func (i integer) print(){ //方法的绑定
fmt.Print1n("i = ",i)
}
func main(){
var i integer = 20
i.print()
}
注意:
- test方法中参数名字随意起。
- 结构体Person和test方法绑定,调用test方法必须靠指定的类型: Person。
- 如果其他类型变量调用test方法一定会报错。
- 结构体对象传入test方法中,值传递,和函数参数传递一致。
(4)方法与函数的差别
- 绑定指定类型:
方法:需要绑定指定数据类型
函数:不需要绑定数据类型 - 调用方式不一样:
函数的调用方式:
函数名(实参列表)
方法的调用方式:
变量.方法名(实参列表) - 对于函数来说,参数类型对应是什么就要传入什么。
- 对于方法来说,接收者为值类型,可以传入指针类型,接受者为指针类型,可以传入值类型。(都是传值)
(5)创建结构体实例
本包:
type Student struct{
Name string
Age int
}
func main(){
var s1 = Student{"Ann", 19} // 按顺序赋值
var s2 = Student{ // 按类型赋值
Age : 20,
Name : "郭哲炜",
}
// 想要返回结构体的指针类型
var s3 = &Student{"Ann", 19}
var s4 = &Student{
Age : 20,
Name : "郭哲炜",
}
}
跨包:
结构体首字母大写,可以跨包访问;
如果小写,则需要引入工厂模式。
// 在非main包中
import "文件夹路径/model"
/* 首字母大写 */
type Student struct{
Name string
Age int
}
s = model.Student{"lili",10}
/* 首字母小写 */
type student struct{
Name string
Age int
}
// 工厂模式
func NewStudent(n string, a int) *student{
return &student{n, a}
}
2.封装
封装的引入:
(1)建议将结构体、字段(属性)的首字母小写(大写其实也行)
(2)给结构体所在包提供一个工厂模式的函数,首字母大写(类似构造函数)
(3)提供一个首字母大写的Set方法,用于对属性判断并赋值
(4)提供一个首字母大写的Get方法,用于获取属性的值
封装的实现:
type person struct{
Name string
age int // 小写的,别的包不能直接访问
}
// 工厂模式函数
func NewPerson(name string) *person{
return &person{
Name : name
}
}
// 定义 Set和 Get "方法"
func (p *person) SetAge(age int){
if age > 0 && age < 150{
p.age = age
} else {
fmt.Println("您输入的年龄有误")
}
}
func (p *person) GetAge() int{
return p.age
}
3.继承
(1)基础
如果一个结构体嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法。从而实现了继承特性。
type Animal struct{
Age int
Weight float32
}
// 绑定方法
func (an *Animal) Shout(){
fmt.Println("啊啊啊啊啊啊啊啊")
}
func (an *Animal) ShowInfo(){
fmt.Println("年龄为: %v,体重为: %v", an.Age, an.Weight)
}
// 定义新结构体
type Cat struct{
Animal
}
// 绑定特有方法
func (c *Cat) scratch(){
fmt.Println("喵喵喵,挠你")
}
func main(){
Cat.Animal.Age = 3
}
(2)注意事项
(1)结构体可以使用嵌套的匿名结构体所有字段和方法(不受首字母影响)。
(2)匿名结构体字段访问可以简化。
(3)结构体和匿名结构体有相同的字段或方法时,使用简化版本时,编译器就近原则。
func main(){
Cat.Age = 3
}
(4)存在多继承(但不建议使用)
(5)匿名结构体有相同字段时,不能简化,以区分
(6)结构体的匿名字段可以是基本类型
(7)嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值。
(8)嵌入匿名结构体的指针也是可以的。
(9)结构体的字段可以是结构体类型的。(组合模式)
4.接口
(1)基础
(1)接口中定义一组方法,无需实现,不能有变量。
(2)所有方法都必须实现,才算接口。
(3)Golang没有显式的接口关键字。
(4)接口的目的是定义规范。
//接口的定义
type Say interface{
sayHello()
}
//接口的实现
type Chinese struct{
}
func (person Chinese) sayHello(){
fmt.Println("你好")
}
type American struct{
}
func (person American) sayHello(){
fmt.Println("hi")
}
//接口接收函数
func greet(s Say){ // 多态是基于接口实现的
s.sayHello()
}
func main(){
c := Chinese{}
a := American{}
greet(a) // 函数方式调用接口
greet(c)
var s Say = a // 指向方法调用接口
s.sayHello()
var s Say = c
s.sayHello()
}
(2)注意事项
(1)接口本身不能创建实例,但可以指向一个实现了该接口的自定义类型的变量
(2)只要是自定义类型变量,就可以实现接口,不仅仅是struct
// 基于上边的代码
type integer int // 自定义的类型
func (i integer) sayHello(){
fmt.Println(i)
}
func main(){
var i integer = 10
var s Say = i // 会输出打印
s.sayHello
}
(3)一个自定义类型可以实现多个接口
//与上述代码无关
type CInterface interface{
c()
}
type BInterface interface{
b()
}
type AInterface interface{
CInterface
BInterface
a()
}
func ...// a(), b(), c() 三个方法都需要被实现
(4)一个接口可以继承多个别的接口
(5)interface类型默认是一个指针(引用类型),默认为nil
(6)空接口没有任何方法:所有类型都实现了空接口,任何一个类型的变量都可以赋值给空接口。
type E interface{
}
(3)多态
多态是基于接口实现的,参考上文(1)中的代码
func greet(s Say){ // 多态是基于接口实现的
s.sayHello()
}
(1)多态参数:如之前的例子
(2)多态数组:定义Say数组,可以存放Chinese结构体、American结构体
//接口的定义
type Say interface{
sayHello()
}
//接口的实现
type Chinese struct{
name string
}
func (person Chinese) sayHello(){
fmt.Println("你好")
}
type American struct{
name string
}
func (person American) sayHello(){
fmt.Println("hi")
}
//接口接收函数
func greet(s Say){ // 多态是基于接口实现的
s.sayHello()
}
func main(){
var arr [3]Say
arr[0] = American{"Ann"}
arr[1] = Chinese{"郭哲炜"}
arr[2] = American{"Peter"}
fmt.Println(arr)
}
6.断言
//接口的定义
type Say interface {
sayHello()
}
//接口的实现
//中国人
type Chinese struct {
name string
}
func (person Chinese) sayHello() {
fmt.Println("你好")
}
func (person Chinese) kuaizi() { //专有函数
fmt.Println("筷子")
}
//美国人
type American struct {
name string
}
func (person American) sayHello() {
fmt.Println("hi")
}
func (person American) hpop() { //专有函数
fmt.Println("Hpop")
}
//接口接收函数,断言实现
func greet(s Say) { // 多态是基于接口实现的
s.sayHello()
/*断言写法1:if判断*/
if value, ok := s.(Chinese); ok {
value.kuaizi()
} else {
fmt.Println("美国人不用筷子")
}
/*断言写法2:switch判断*/
switch s.(type) {
case Chinese:
temp := s.(Chinese)
temp.kuaizi()
case American:
temp := s.(American)
temp.hpop()
default:
fmt.Println("未知的类型")
}
}
func main() {
c := Chinese{"郭哲炜"}
a := American{"Peter"}
greet(a)
greet(c)
}
六、文件操作
1.文件
打开和关闭文件:
import (
"os"
"fmt"
)
func main(){
//打开文件
file, err := os.Open("d:/Test.txt")
if err != nil{ //出错
fmt.Println("文件出错,问题为:",err)
}
//没有出错,输出文件
fmt.Printf("文件=%v",file)
/*关闭文件 方法1*/
err2 := file.Close();
}
if err2 != nil{ //出错
fmt.Println("关闭失败")
}
2.io读取文件
内含有Open和Close的函数的操作:
import (
"fmt"
"io/ioutil"
)
func main(){
//读取文件:
content, err = ioutil.ReadFile("d:/Test.txt") //返回内容为[]byte,err
if err != nil{ //出错
fmt.Println("读取出错",err)
}
//成功
fmt.Printf("%v",string(content))
}
不内含有Open和Close的函数的操作(推荐):
import (
"bufio"
"fmt"
"io"
"os"
)
func main() {
//打开文件
file, err := os.Open("d:/Test.txt")
if err != nil { //出错
fmt.Println("文件出错,问题为:", err)
}
/*关闭文件 方法2*/
defer file.Close()
//创建一个流
reader := bufio.NewReader(file)
//读取方法
for {
str, err := reader.ReadString('\n') //读到换行截止
fmt.Println(str)
if err == io.EOF {
break
}
}
}
3.io写入文件
函数:
func OpenFile(name string, flag int, perm FileMode) (file *File, err error)
(1)参数一:文件路径
(2)参数二:文件打开模式(用“|”组合)
const (
O_RDONLY int = syscall.O_RDONLY // 只读模式打开文件
O_WRONLY int = syscall.O_WRONLY // 只写模式打开文件
O_RDWR int = syscall.O_RDWR // 读写模式打开文件
O_APPEND int = syscall.O_APPEND // 写操作时将数据附加到文件尾部
O_CREATE int = syscall.O_CREAT // 如果不存在将创建一个新文件
O_EXCL int = syscall.O_EXCL // 和O_CREATE配合使用,文件必须不存在
O_SYNC int = syscall.O_SYNC // 打开文件用于同步I/O
O_TRUNC int = syscall.O_TRUNC // 如果可能,打开时清空文件
)
(3)权限控制(linux/unix才生效)
import(
"fmt"
"os"
"bufio"
)
func main(){
//打开文件
file, err := os.OpenFile(d:/Demo.txt, os.O_RDWR | os.O_APPEND | os.O_CREATE, 0666)
if err != nil{
fmt.Println("打开文件失败", err)
return
}
//关闭文件
defer file.Close()
//写入文件:IO流→缓冲输出流(带缓冲区)
writer := bufio.NewWriter(file)
for i := 0; i < 10; i++{
writer.WriteString("你好 郭哲炜\n")
}
//流带缓冲区,刷新数据→真正地写入文件中:
writer.Flush()
}
4.文件复制操作
读取+写入(不推荐使用ioutil):
import(
"fmt"
"io/ioutil"
)
func main(){
//定义源文件
file1 := "d:/Demo.txt"
//定义目标文件
file2 := "d:/Demo2.txt"
//对文件进行读取
content, err := ioutil.ReadFile(file1)
if err != nil{
fmt.Println("读取失败")
return
}
//写出文件
err2 = ioutil.WriteFile(file2,content,0666)
if err2 != nil{
fmt.Println("写出失败")
return
}
七、协程和管道
1.基本概念
(1)程序
是为了完成特定任务,用某种语言编写的一组指令的集合,是一段静态的代码。
(2)进程
是程序的一次执行过程,进程作为资源分配的单位,在内存中会为每个进程分配不同的内存区域。它是动态的,有自身产生、存在和消亡的过程。
(3)线程
进程可进一步细化为线程,是一个程序内部的一条执行路径。
(4)协程
又称为微线程,是一种用户态的轻量级线程。
2.协程
(1)多协程
协程可以多个,但是主程序结束时,协程也会跟着结束。
func main(){
//启动多个协程
for i := 1; i <= 5; i++{
go func(){
fmt.Println("hello world")
}()
}
}
(2)延长协程存活
延长协程的存活时间 sync.WaitGroup
import (
"fmt"
"sync"
)
var wg sync.WaitGroup //只定义无需赋值
func main(){
//启动多个协程
for i := 1; i <= 5; i++{
wg.Add(1) //协程开始时加一
go func(){
fmt.Println("hello world")
defer wg.Done() //协程结束时减一,可以结合defer使用
}()
}
//主线程一直在阻塞,什么时候wg减为0,则停止
wg.Wait()
}
(3)互斥锁同步协程
解决多个协程操作一个数据,结果混乱的情况。但互斥锁效率比较低
import (
"fmt"
"sync"
)
var totalNum int
var wg sync,WaitGroup
//互斥锁
var lock sync.Mutex
func add(){
defer wg.Done()
for i := 0; i < 1000; i++{
//加锁
lock.Lock()
totalNum = totalNum + 1
//解锁
lock.Unlock()
}
}
func sub(){
defer wg.Done()
for i := 0; i < 1000; i++{
//加锁
lock.Lock()
totalNum = totalNum - 1
//解锁
lock.Unlock()
}
}
func main(){
wg.Add(2) //启动协程
go add()
go sub()
wg.Wait() //阻塞主进程
fmt.Println(totalNum)
}
(4)读写锁
写和读之间才会产生影响,单单在读的时候不会彼此产生影响。
import (
"fmt"
"sync"
"time"
)
var totalNum int
var wg sync,WaitGroup
//互斥锁
var lock sync.RWMutex
func read(){
defer wg.Done()
lock.RLock() //如果只是读数据,则这个锁不会产生影响,同时读写则会有影响
fmt.Println("开始读取数据")
time.Sleep(time.Second)
fmt.Println("读取数据完毕")
lock.RUnlock()
}
func sub(){
defer wg.Done()
lock.Lock() //如果只是读数据,则这个锁不会产生影响,同时读写则会有影响
fmt.Println("开始写入数据")
time.Sleep(time.Second*10)
fmt.Println("写入数据完毕")
lock.Unlock()
}
func main(){
wg.Add(2) //启动协程
for i := 0; i < 5; i++{
go read()
}
go write()
wg.Wait() //阻塞主进程
}
3.管道
本质是数据结构里的队列,先进先出,线程安全无需加锁。
管道是有类型的,一个string的管道只能放string类型。
var intChan chan int //声明定义
intChan = make(chan int, 3) //初始化空间
intChan <- 10 //存放数据
num := <- intChan //取出数据
在没有使用协程的情况下,如果数据已经全部取出,再取会报错(不能放多,不能取多)
(1)管道关闭
可以取,不可放。
close(intChan)