go笔记-3

并发和并行

并发是只一次做多件事情,但是同一时间只做一件事情
并行是一次做多件事情,并且这些事情在同一时间内都在处理

在函数前加入go关键字来新建一个并发的Goroutines
go funcName(1)
任何Gorountines都依附在main goroutines上,因此主进程结束时所有Goroutines都会立即结束,即使没有处理完毕

channels

类型前加入chan关键字来声明,如
var a chan int
a := make(chan int)
前者a == nil,后者则定义了一个名为a的channel

channel的数据读写

1
2
data := <- a//从a中读取数据,直到读取到数据才往下运行
a <- data //向a中写入数据

用法举例

1
2
3
4
5
6
7
8
9
10
func hello(done chan bool) {  
fmt.Println("Hello world goroutine")
done <- true
}
func main() {
done := make(chan bool)
go hello(done)
<-done
fmt.Println("main function")
}

若channels写入数据但是该通道没有地方读取该数据,将会造成deadlock错误
参数传入时,可以使用chan<- int,把传入的双向通道改为只写通道,函数内只能向通道写入数据,不能读取数据

通道中的每一条消息只能被收到一次,即使有多个函数试图接收,也只会有一个收到
可以使用’close(ch)’关闭ch通道,此时所有没有收到的都会收到关闭的数据
·v,ok<-ch的ok来判断ch通道是否是因为close而关闭,若关闭,收到的值为对应数据的零值for v:=range ch`可以一直循环直到ch关闭

buffered channels

发送时若buffer满了则会阻塞,受到时若buffer为空则阻塞
声明方法
ch := make(chan type, capacity)
capacity为buffer的容量,意味着发送时未被收接收的数据存放数量一旦高于就会阻塞
可以通过len(eh)获取还没有处理的数据,cap(ch)获取容量

waitgroup

声明方法var wg sync.WaitGroup
wg.Add(1)添加计数,wg.Down()减少计数
wg.Wait()等待直到计数归零
waitgroup作为实参传递的时候,需要加上&防止函数内部传到的是副本而不是本身

workpool

处理的任务的方式

(并发1个)输入函数alloacate,将输入传递到’输入用channels’中
(并发n个)处理函数worker,从‘输入用channels’获取数据,处理后放在’输出用channels’
(并发1个)输出函数result,从’输出用channels’中获取数据用于输出

其中’输入用channels’和’输出用channels’为buffered pool,可以储存一定量的数据供处理
n个worker函数使得任务可以一次并发处理多个

个人认为好处可能是是当其中1个任务工作量太大的时候,依旧可以处理其它工作量较小的任务,而不至于被大任务完全阻塞?

select

1
2
3
4
5
6
select {
case s1 := <-output1:
fmt.Println(s1)
case s2 := <-output2:
fmt.Println(s2)
}

output1,output2是channel
类似switch,但是是用来处理channel的,当有channel收到数据的时候,就会执行该分支,在这之前将会阻塞
若同时收到数据的有多个,则随机选择一个分支执行

分支可以使用default,代表没有任何一个case的channel收到了数据则执行
个人认为存在default分支的select不会造成阻塞,因为要么收到了数据随机执行分支,要么没有收到执行default,不会有等待的时刻

select中default的使用可以避免死锁
空的select会造成永久阻塞

Mutex

临界区(Critical section)

每个线程中访问临界资源的那段程序称为临界区(Critical Section)(临界资源是一次仅允许一个线程使用的共享资源)。每次只准许一个线程进入临界区,进入后不允许其他线程进入。不论是硬件临界资源,还是软件临界资源,多个线程必须互斥地对它进行访问。
摘自百度百科

1
2
3
mutex.Lock()  
x = x + 1
mutex.Unlock()

mutex在Lock时会检测是否已经上锁了,如果有,则阻塞直到原先已经上锁的那边的锁的解除(即Unlock被执行)
可以防止因为多个Goroutines并发,同时修改同一个数据造成的数据错乱
和WaitGroup一样,传递Mutex变量需要使用&来确保不同函数传入的是同一个而非拷贝,因为Mutex只有在同一个变量上锁的情况下才会阻塞

也可以使用channel达到与mutex相同的效果,令ch为capacity为1的buffered channel

1
2
3
ch<-true//存在数据,其它函数内的ch只能阻塞直到这个数据被接收
x = x+1
<-ch//接收ch内的数据,其它函数内的ch现在可以解除阻塞来传入数据了

“仿造”Class

因为go里并没有提供’构造函数’功能,我们只能声明一个函数,假装它是构造函数:

将struct用小写(比如说 a)设置为外部不可见,这样外部就不能直接声明变量来获得这个类型
构造一个函数(比如用Nwe命名),生成一个a类型,并且为其赋值,然后返回这个a类型
由于外部只能通过这个函数生成到这种类型的数据,这个函数将充当构造函数

Defer

在函数或者方法前面添加defer关键词,可以让它在return之前才调用,但是保留当时参数的值,如

1
2
3
4
5
6
7
8
9
10
func printA(a int){
fmt.Println("PrintA:",a)
}

func print(){
a:=5
defer printA(a)
a=10
fmt.Println("Print:",a)
}

输出为

1
2
Print: 10
PrintA: 5

多个defer采用栈(LIFO)后进先出的顺序执行

可用于有多个return分支但是又希望在return前进行统一动作的时候使用

Error

自带有error接口,只要声明了Error()函数的类型,就可以作为错误输出数据
此时Println()函数会调用该数据的Error,输出其返回的string
errors.New("错误信息")也可以用来创建新的错误数据
fmt.Errorf("错误信息,附带值:%d",10)同样可以用来创建新的错误数据

通过类型断言来转化错误类型获得更多信息
比如说对于打开文件f,err:=os.Opem("path/"),可以断言err
if err, ok := err.(*os.PathError);ok这样就可以用err.path获取到路径信息

func panic(interface{})可以用来输出那些可能会导致程序无法继续的错误
调用该函数后,调用者函数将会被中止,未处理的deder的函数将会被处理
然后以同样的方式中止(调用(调用(panic函数)的函数)的函数),逐级向外推进,直到推进到没有外层函数为止(即程序中止)
程序中止时,将输出调用panic函数的位置追踪。

可以在deder中调用func recover() interface{}来中止panic向外推进的过程
例如在

1
2
3
4
5
func recoverName() {  
if r := recover(); r!= nil {
fmt.Println("recovered from ", r)
}
}

那么在调用defer recoverName()的函数不再向外层传递panic的影响(但调用函数本身依旧受到影响)
这使得panic只限制于一个范围内,而非让整个程序终止
recover只限于恢复同一个goroutine内的函数,无法在一个goroutine下恢复另一个goroutine造成的panic

runtime error也是内建的panic,所以也可以通过recover恢复

recover会中止错误路径追踪,如果需要追踪,则要在recover的函数内添加debug.PrintStack()函数来打印它

一级函数

(关于类型的分类资料)[http://rednaxelafx.iteye.com/blog/184199]
类型:规定了变量可以取的值得范围,以及该类型的值可以进行的操作。根据类型的值的可赋值状况,可以把类型分为三类:

1、一级的(first class)。该等级类型的值可以传给子程序作为参数,可以从子程序里返回,可以赋给变量。大多数程序设计语言里,整型、字符类型等简单类型都是一级的。
2、二级的(second class)。该等级类型的值可以传给子程序作为参数,但是不能从子程序里返回,也不能赋给变量。
3、三级的(third class)。该等级类型的值连作为参数传递也不行。

所以一级函数就是能作为参数传递的函数

允许闭包,允许匿名函数,允许函数作为参数或者返回值

go笔记-2

大部分例子和资料来自于这里

关于数组

数组声明时[]放在类型前面,如i := [5]int
i := [...]int{1,2,3}可以自动判断数组大小
数组传参是按值传递的(但slice是按引用的,见下),会创建副本,而非引用原来的数组
可以用len(i)获取数组长度
可以用for index,value := range(i)遍历数组
不同长度的数组类型是不同的

关于slice

参考资料

slice内部有三个变量len,cap,ptr
ptr是指向底层数组的指针
cap是ptr的容量
len是slice的长度(含有元素的个数)

没有元素的slice类型是nil,cap和len为0
a:=i[1:2]切片slice,切出来的部分会随着原数组对应值改变而改变
可以用i[:]代表整体的切片
b:=make([]int,0,10)也可以用来创建slice,此时len(a)为0,cap(a)为10

a = append(a,1,2,3,4)可以对切片添加数据,或b = append(b,a...)来添加切片a的元素值到b

slice长度变化(扩容)

如果新的大小是当前大小2倍以上,则大小增长为新大小
否则循环以下操作:如果当前大小小于1024,按每次2倍增长,否则每次按当前大小1/4增长。直到增长的大小超过或等于新大小。

1
2
3
4
a := [...]int{1}
b := append(a[0:1], 1)
b[0]++
fmt.Println(a,b)

输出[1] [2 1]
切片的cap是被切片数组起始元素到数组末尾的元素个数(1-0=1),而len在此处是切片的长度(1-0=1)
添加元素后len+1<cap,需要扩容
扩容之后由于内存地址重新分配,值为原先的拷贝而非引用,因此修改值不会反映到原数组上

由于append的长度不需要变化的时候ptr还是原来的值,这时候append是直接通过修改ptr上的元素来实现的

1
2
3
4
a:=make([]int,3,5)
x:=append(a,1)
y:=append(a,2)
fmt.Println(x,y)

输出[0 0 0 2] [0 0 0 2]
可见x本来应该是1的值因为y的append变成了2
因为a不需要扩容(len(a)+1=3+1<=5=cap(a)),x,y指向的还是a的ptr,append在a的len往后一位修改内存
append不会改变传入数组的属性,a的len不变,因此两次append都是往a的第四个元素修改
如果第3句改为y:=append(x,2)就不会出现上述的问题,因为x的len是4,会后移一位修改,此时y的输出将会是[0 0 0 1 2]

1
2
3
4
a := [...]int{1, 2, 3, 4}
b := a[0:1]
c := append(b, 1)
fmt.Println(a, b, c)

输出[1 1 3 4] [1] [1 1]
也同样反应了上述观点,len=1-0=1,cap=4-0=4,len+1<cap,没有扩容
直接往b的len的下一位修改,而b的ptr指向的是a的第0个元素,所以修改的是a的第1个元素,即a的元素2被改成了1

嗯……总之go的slice使用的时候要注意

可变参函数

在类型前面加上...(限最后一个参数使用)

1
2
3
4
func f2(num int,nums ...float64){
fmt.Println(nums[0],len(nums))
//do something
}

此时传入的nums类型为 []float64,或者说float的slice

f2可以接受多个float64参数,或者一个slice(需要在后面加上...,一个语法糖)

1
2
3
4
f2(1,1.1,1.2,1.3,1.4)

a:=[...]float64{1.1,2.2,3.3}
f2(1,a[0:3]...)

如果传入了slice,函数内部对slice内元素的修改将会反映到原来的slice上(可以理解为对指针的修改?)

map

1
2
3
4
5
6
a := map[string]int
b := make(map[string]int)
a["abc"] = 1
value,ok := a["def"]//ok==false
delete(a,"abc")
fmt.Println(len(a))

map是引用类型

string 和 []rune

string以十六进制编码,下标对应1个字节,以下标访问时,如果是unicode且占2字节的东西用%c显示的将不是预期的结果
rune是int32的别名,rune数组转换的string,下标对应1个字符(不论编码),用%c即使是2字节的unicode也可以正常显示

1
2
s:="Hello world"
a:=[]rune(s)

遍历字符

1
2
3
4
s:="Hello World"
for index,rune := range s{
//index代表该字符占的字节数,rune代表该字符
}

[]rune和[]byte可以转string

1
2
3
4
5
byteSlice := []byte{0x43,0x61,0xC3,0XA9}
str1:=string(byteSlice)

runeSlice := []rune{0x0053, 0x0065, 0x00f1, 0x006f, 0x0072}
str2:=string(runeSlice)

string的utf8编码下字符长度用utf8.RuneCountInString(s)
string是不可修改量,可以通过下标获取,但是不能修改,要修改可以先转为[]rune,改完后再转回string

指针

···
var a *int
//a==nil

b:=1
c:=&b
*c++
···

对数组的指针传参

1
2
3
func modify(arr *[3]int){
arr[0] = 1
}

此时arr[0]是(*arr)[0]的缩写

一般更习惯用slice进行数组修改,而非数组指针

1
2
3
func modify(sls []int){
sls[0] = 90
}

调用方式

1
2
a:=[3]int{1,2,3}
modify(a[:])

不支持指针修改,p++之类的操作是不行的

struct

定义和使用

1
2
3
4
type Employee struct {  
firstName, lastName string
age, salary int
}

1
2
emp2 := Employee{"Thomas", "Paul", 29, 800}
var a Employee

匿名struct

1
2
3
4
5
6
7
8
9
emp3 := struct {
firstName, lastName string
age, salary int
}{
firstName: "Andreah",
lastName: "Nikola",
age: 31,
salary: 5000,
}

对于struct指针,可以用a.abc代替(*a).abc来访问a中的属性abc

对于struct嵌套,若未声明变量名(即定义如下时,假设Abc为另一个struct,有def属性)

1
2
3
type A struct{
Abc
}

则可以用a.def代替a.Abc.def来访问a中struct Abc中的def变量(下述的’方法’同理)

当成员变量都可相等比较的时候,struct才可以相等比较,当所有成员都相等时认为相等

方法

1
2
3
func (e Employee) displaySalary() {  
fmt.Printf("Salary of %s is %s%d", e.name, e.currency, e.salary)
}

这样可以定义一个Employere的方法,但是这样的方法修改变量不会影响到原变量,传入的e是原来e的拷贝
要影响需要用指针,即func (e *Employee) displaySalary()
对于这种形式的方法,e.dispaleSalary()将是(&e).displaySalary()的简写

接口

定义

1
2
3
type A interface{
abc()[]int
}

当一个struct实现了所有接口的方法,它将会自动应用接口,而不需要自己显式地添加什么

实现了接口的struct可以被赋值给接口类型,如果a变量的类型实现了A接口,那么就可以var b A:=a

任何数据都应用了空接口interface{}(没有任何方法的接口),所以可以用空接口来为属性传入任何类型的参数
可以利用a := i.(int)来断言传入的参数i类型为int,如果不是则报运行时错误
可声明ok变量,这样就不会报运行时错误,然后根据ok变量的值做进一步的判断

1
2
3
4
5
6
func assertion(i interface{}){
s,ok:=i.(int)
if ok {//是int类型
fmt.Println(s)
}
}

字面量作为这种函数的参数的时候,将会转化为默认类型,传入参数为字面常量8时,断言int是true,int8为false
可以在switch中使用断言,断言的类型判断也可以是接口

1
2
3
4
5
6
7
8
switch i.(type){
case string:
//do something
case int:
//do something
default:
//do something
}

接口可以嵌套,可以声明一个接口,里面嵌套另一个接口
未赋值的接口变量为nil

go笔记-1

大部分资料来自于这里

关于变量

声明方式

1
2
3
4
5
6
7
var a int = 1
var b, c int = 2, 3
d, e := 4, 5
var (
f = 6
g int = 7
)

关于常量

对于没有设定类型的常量,是没有类型的
比如说const i = 1
可以有var a int = i或者var b int8=i
在需要的地方它才会转化类型,就像是字面常量一样

关于函数

函数可以有多个输出(此时需要用括号括起来),下划线_可用来抛弃一些函数的返回值

1
2
3
4
5
6
7
func f(a,b int)(c,d int){
c=a
d=b
return
}

_,c = f(1,3)

此时c为3

1
2
3
4
5
6
7
8

## 关于包
只有首字母大写的函数和变量才能够在被其他包引用时调用
包内可以声明
```go
func init(){
//do something
}

进行包的初始化,init()函数会在包内变量初始化完毕后执行
一个包可以有多个init(),不管是在同一个文件内还是放在包内的不同文件中
即使被import多次,init()也只会执行一次,

可以在包前面加入下划线,代表我们需要这个包的init(),但是不一定在代码里用上这个包

1
import _ "github.com/go-sql-driver/mysql"

用下划线_赋值包内函数或变量,则可以在开发时暂时禁用未使用包的错误,等真的使用的时候再删除

关于if

else和花括号必须在同一行,除非你用了分号

1
2
3
4
5
if {
//do something
}else{
//do something
}


1
2
3
4
5
6
if {
//do something
};
else{
//do something
}

if内可以塞语句,语句会在条件判断前执行

1
2
3
if x:=10;x>=10{
//do something
}

关于循环

for 内可以只塞条件,类似其他语言的while一样使用
没有条件代表无限循环

1
2
3
4
5
6
7
for x==1 {
//break when x==1
}

for {
//infinite
}

关于switch

switch内可以塞语句,语句会在条件判断前执行
case中会自动在结尾加上break(对于不想自动添加break的,可以在那一段case内末尾加入fallthrough关键字)

1
2
3
4
5
6
7
switch x:=1; x{
case 1,2,3:
//x is 1 or 2 or 3
fallthrough
default:
//do something
}

显式下厨break也不会报错,但是只会跳出switch判断,如

1
2
3
4
5
6
7
x:=1
for{
switch x{
case:1
break
}
}

只会跳出break而不是for,依旧会无限循环下去
要跳出for,可以这样

1
2
3
4
5
6
7
x:=1
myloop: for{
switch x{
case 1:
break myloop
}
}

case条件 不一定 是常量,switch为空的时候,匹配条件为true的

1
2
3
4
5
6
switch {
case x<2:
//x is less than 2
case x<3:
//x is in range [2,3)
}

VSCode go 插件安装

由于部分插件受限于国内网络原因无法下载安装,如果没有科学上网的话,可以尝试以下方式
来自这里
在go工作目录下的src/golang.org/x中克隆https://github.com/golang/tools仓库
(路径:src/golang.org/x/tools/各种文件)
然后执行

1
2
3
4
5
6
7
go get -u -v github.com/rogpeppe/godef
go get -u -v github.com/golang/lint/golint
go get -u -v github.com/lukehoban/go-outline
go get -u -v sourcegraph.com/sqs/goreturns
go get -u -v github.com/tpng/gopkgs
go get -u -v github.com/newhook/go-symbols
go get -u -v golang.org/x/tools/cmd/guru

部分可能下载失败,这部分需要使用

1
2
3
4
5
go install github.com/golang/lint/golint
go install sourcegraph.com/sqs/goreturns
go install golang.org/x/tools/cmd/gorename
go install github.com/newhook/go-symbols
go install golang.org/x/tools/cmd/guru

个人尝试安装glint还是失败,于是去
https://github.com/golang/lint
直接将东西下载下来放在src/golang.org/x下(文件夹名字是lint)
再执行go install github.com/golang/lint/golint

如果其他库也安装失败,应该也可以在git上找到对应的库用类似的方法解决?(不太确定)

无脑配服务器/Nginx/MySQL/mongoDB/go/redis

安装

1
2
3
4
5
6
7
sudo apt-get update
sudo apt-get install bison ed gawk gcc libc6-dev make -y
sudo apt-get install nginx -y
sudo apt-get install mongodb-server -y
sudo apt-get install redis-server -y
sudo apt-get install mysql-server -y
sudo apt-get install golang -y

MYSQL安装需要输入root账户的密码



nginx配置

创建nginx用权限账户 www(或者在配置文件中修改user为已存在用户)

1
2
sudo /usr/sbin/groupadd www
sudo /usr/sbin/useradd -g www www

(可能会报sudo: unable to resolve host,这只是warning,可以不用管,下同)

修改配置文件(参考文件中有参考网址可以查看参数的作用)
sudo vim /etc/nginx/nginx.conf

/etc/nginx/conf.d/*.conf;
/etc/nginx/sites-enabled/*;
这两个是配置文件中带有的目录,网站的参数也可以在这里进行修改

默认在”/etc/nginx/sites-enabled/default”中存在80端口的配置
如果自己需要修改80端口的话,可以直接修改这个文件,或者删掉自己重新配置

重启nginx适用配置

sudo service nginx restart


MySql配置

登陆mysql并创建数据库NEST,远程访问用户bird,并授权

1
2
3
4
5
mysql -u username -p password
create database NEST
CREATE USER 'bird'@'%' IDENTIFIED BY 'password here';
GRANT ALL ON NEST.* TO 'bird'@'%';
flush privileges;

修改配置文件
sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf
修改bind-address为0.0.0.0

重启mysql适用配置
sudo service mysql restart



mongoDB配置

(记得在防火墙打开27017端口)

添加用户数据
(创建了超级权限的root用户,和一个只读写nest数据库的bird用户)

1
2
3
4
5
6
mongo
use admin
db.createUser({user:"root",pwd:"password here",roles:["root"]})
use nest
db.createCollection("nest")
db.createUser({user:"bird",pwd:"password here",roles:[{role:"readWrite",db:"nest"}]})

然后退出mongo
用户添加参考
内置权限参考
自定义权限参考

修改配置文件
sudo vim /etc/mongodb.conf
注释”bind_ip=127.0.0.1”一行,以允许远程访问
取消注释”auth = true”一行,以启用权限管理

重启mongodb适用配置
sudo service mongodb restart



redis配置

(记得在防火墙打开6379端口)
如果想要允许远程访问的话,修改配置文件即可
sudo vim /etc/redis/redis.conf
bind 127.0.0.1改为bind 0.0.0.0
取消requirepass foobared注释,并且将foobared改为你的密码,如requirepass "your password"
重启redis服务
sudo service redis-server restart

修改之后在redis-cli中需要添加-a "your password"参数来启动(your password改为你设置的密码)

不添加该参数虽然也可以登录成功,但是没有任何操作权限。


go配置

创建工作目录,配置环境变量和项目目录

1
2
3
4
5
6
cd $HMOE
mkdir Applications Applications/Go
mkdir Applications/Go/src Applications/Go/pkg Applications/Go/bin
export GOROOT=$HOME/go
export PATH=$GOROOT/bin:$PATH
export GOPATH=$HOME/Applications/Go


new Page

久违的又一篇文章
为了某项目而学习go语言的记录大概会放在这里?
大概吧……

1
2
3
4
5
6
7
8
package main
package main

import "fmt"

func main() {
fmt.print("HelloWorld!")
}

first page

嗯,总之就是简单的第一篇文章。

听说有引用这种东西,所以拿来试试

简陋的小屋Wuny
++
1
printf("Hello world!");
|