GoLang学习

Scroll Down

作为一名CRUD Boy 深感Java今年的求职状况,离职一时爽,求职火葬场,多学一门语言保狗命。遂入坑GOLang. 用Go的前提不为装X,只是为了go的协程,手动真香。直接语言层面的微线程。丝滑。毕竟我是个CRUD Boy刚开始只能业务代码入手下。

image

废话不多逼逼,直接看安装环境开始学,学习环境在我的破旧的ThinkPadT440p上,直接使用官方提供的WIndows安装包,傻瓜安装,安装程序直接帮你吧环境变量都设置好了,不得不说还真的傻。安装完毕之后再cmd命令行 查看版本输出信息如下就可以上手开发了。

C:\Users\wangzhifei>go version
go version go1.12.1 windows/amd64

先搞个hello World,进入装X模式,func main() {fmt.Println("hello World")},yeah 美滋滋,我会goLang。够沙雕,可以做朋友。ok先看看golang的语法,首先看下goLang标识符应该有65个分别是25个关键字标识符,20个数据类型标识符,4个常量标识符,1个空白标识符,15个内置函数。
这20个关键字中除了见名知意的之外特定的需要说下defer,go,chan,map,struct,interface,select

  • defer

被defer修饰的代码块会在函数return之后执行,所以通常用来释放函数内部的变量占用,例如文件句柄,还有使用defer修饰的代码块,其内的参数会被实时解析,还有就是一个函数中如果有多个defer块,那么其执行顺序是按照先进后出来执行的,还有defer可以读取到返回值然后进行操作。

defer用法
package main
import (
"fmt"
)
func write(ch chan int) int{
	for i:=0;i<10;i++{
		ch <- i
	}
	defer close(ch)
	return 0
}
func demo() {
i := 0
defer fmt.Println(i)
i++
return
}
func demo2() i int {
i := 0
defer func() { i++ }()
i++
return i
}

上述代码write方法 会在返回之后关闭通道。一般用于资源释放,demo方法输出0,demo2方法会把返回值加加。从上免得执行结果来看,defer关键字有点类似于java的finally关键字。。。

  • chan

chan是golang中的阻塞通道,用来做goroutine的通信,因为golang程序必然会有多个goroutine,通道用来同步这些goroutine。符号<-进行读取或者写入,譬如value,ok := <-c是读取,c <- value 是写入,读取的时候没有ok当然是没有问题,就是不知道通道是不是已经关闭,还有无法向已经closed的chan写入,所以一般写入时需要用一个信号的chan(一般buffer为1),来判断是否写入或者放弃,用select判断是写入成功了,还是正在关闭需要放弃写入。如果closed后,chan有数据,ok还是true的,直到chan没有数据了才false。通道是同步且无缓冲的。这种特性导致通道的发送/接收操作,在对方准备好之前是阻塞的。缓冲通道,在缓冲满载(缓冲被全部使用)之前,给一个带缓冲的通道发送数据是不会阻塞的,而从通道读取数据也不会阻塞,直到缓冲空了

chan用法
package main
import (
"fmt"
)
func main() {
	fmt.Println("...")
	c := make(chan int, 1)
	c <- 10
	v,ok := <- c
	fmt.Println(v,"...",ok)
	//close(c)
	v1,ok1 := <- c 
	fmt.Println(v1,"...",ok1)
}
func main() {
	fmt.Println("...")
	c := make(chan int,2 )
	c <- 10
	c <- 10
	v,ok := <- c
	fmt.Println(v,"...",ok)
	//close(c)
	v1,ok1 := <- c
	fmt.Println(v1,"...",ok1)
}

上述代码1会提示报错: all goroutines are asleep - deadlock! 卧槽为啥死锁了。因为没有调用chan的close,所有此时所有的读取都会被阻塞,所以系统就提示了死锁,只有缓存通道为空才会发生死锁,代码2不会提示报错是因为缓存通道不为空时候不会发生死锁。所以正常使用chan的方式一般是要读取端使用多个变量读取和写入端close方法配合使用。如下所示

func read(ch1 chan int){
	for{
		v ,ok:= <- ch1
		if ok{
			fmt.Printf("read a int is %d\n",v)
		}
	}
}
func write(ch chan int){
	for i:=0;i<10;i++{
		ch <- i
		fmt.Printf("write a int is %d\n",i)
	}
	close(ch)
}
func main() {
	ch1 := make(chan int)
	go write(ch1)
	go read(ch1)
	time.Sleep(2222)

}

配合select的使用

func main() {
	fmt.Println("...")
	c := make(chan int, 1)
	select {
	case c <- 1: 
	default:
	}
	select {
	case c <- 2:
	default: // c中只有1,没有2
		fmt.Println("无法写入...")
	}
}

上述代码结果
因为通道的缓冲区为1,已经输入1个,所以就已经无法写入通道了,在select就输出无法写入....

func main() {
	c := make(chan int, 1)
	c <- 10
	close(c)

	v1,ok1 := <- c 
	v,ok := <- c 
	if ok1 {
      // c=10,ok=true,读取出来一个
		 fmt.Println("读取结果",v1)
	}
	if ok {
      // c=0,ok=false,实际上没有读出来
		 fmt.Println(v)
	}
}

上述代码结果: 读取结果 10

  • go

go关键在GOLang中用于开启goroutines,微线程,直接从语言层面支持并发,不像java则是jvm内部映射到OS上的线程,当主线程执行推出的时候,所有的微线程都会被强行kill掉,每个通过go修饰的语句,都会开启一个协程。例如下面的场景,我们需要搬运一百个箱子,在单线程模式下,就只能一个一个的搬到车上,然后再从车上往下搬,这时候就非常的耗时。使用go关键字,就相当于找了一百个人去干这个事情,然后这个效率瞬间就上去了。这也就是串行和并行的区别。

go用法
package main

import (
	"fmt"
)
var N int=100;
func Task(boxId int)  {
	fmt.Println("搬箱子",boxId)
}
func main() {
	ack := make(chan bool,N)
	for i:=0;i<N;i++{
		go func(arg int) {
			 	Task(arg)
			 	ack<-true;
		}(i)
	}
	for i:=0;i<N;i++ {
		<-ack
	}
}

看上述的代码就是使用go关键字,新开了100个微线程来网通道中录入数据,这样看似是没有问题的,但是如果系统只能创建99个微线程的话,这样就会有问题了,资源得不到释放,而且还在新申请资源,这样就造成了死锁。还有一种情况就是认为有1000个,我们系统只能创建500个,那这样的就就会带来频繁的上下文切换,所以要搞一个类似线程池的设计来处理这个问题。

package main

import (
	"fmt"
)

var N int=100;
var RUNING int=50;
func Task(boxId int)  {
	fmt.Println("搬箱子",boxId)
}
func main() {
	ack := make(chan bool,N)
	works:=worker(func(i int) {
		Task(i)
		ack<-true;
	})
	for i:=0;i<N;i++ {
		works<-i
	}
   for i:=0;i<N;i++ {
		<-ack
	}
}
func worker(tast func(int)) chan int {
	input:= make(chan int)
	for i:=0;i<RUNING;i++{
		go func() {
			for{
				v,ok:=<-input
				if ok {
					tast(v)
				}else {
					return
				}
			}
		}()
	}
  return input;
}

这样的处理方式创建一个有RUNING个数的微线程池,所有对于通道的监听在闭包中处理,每创建一个微线程,都会对通道的状态进行判断,如果通道已经关闭,那么就销毁微线程。最终返回了一个可提交任务的微线程池,然后再main方法中创建一个容量为50个微线程池子,然后该线程池的容量为100个,就是说在执行其余任务的时候会有上下文的切换,这样可控的设计,可以使得程序不会因为频繁的创建微线程,资源不够而死锁,但是代价就是有一定的抖动会发生。

package main

import (
	"fmt"
)

var N int=100;
var RUNING int=50;
func Task(boxId int)  {
	fmt.Println("搬箱子",boxId)
}
func main() {
	exit:=make(chan  bool)
	threadpool :=ThreadPoolFunction(func(i interface{}) {
		Tast(i.(int))
	}, func() {
		exit<-true
	})
	for i:=0;i<N;i++{
		threadpool<-i
	}
	defer close(threadpool)
	<-exit
}

func ThreadPoolFunction(tast func(interface{}),monitor func()) chan interface{} {
	input:= make(chan interface{})
	ack := make(chan bool)
	for i:=0;i<RUNING;i++{
		go func() {
			for{
				v,ok:=<-input
				if ok {
					tast(v)
					ack <-true
				}else {
					return
				}
			}
		}()
		go func() {
			for i:=0;i<RUNING;i++{
				<-ack
			}
			monitor()
		}()
	}
	return input;
}

上诉代码则屏蔽了微线程池版本中的main方法中的需要一个循环来监控所有的微线程是否都执行完毕了,是使用一个新的bool通道拉标识所有的微线程是否处理完毕,并且在创建的时候微线程池的时候指定了2个参数,第一个为任务chan空实现,第二个方法为监控方法空实现,针对在微线程池版本的main方法的改进措施是在微线程池内部新开一个微线程去统计所有的任务微线程是否均执行完毕了,如果执行完毕就去执行monitor方法,对通道的读取是阻塞的,所有阻塞读取完ackchan之后只有就把exit通道中放入一个true标识,然后唤醒main方法中的<-exit实现异步转同步。使用defer关键字去close(threadpool),是为了在任务处理结束之后去释放资源。

安装第三方的库

goLang 需要引入第三方包使用 go get 来安装,如果有vpn则可以直接从go官网进行安装,国内用户可以使用github上的第三方库

go get github.com/go-sql-driver/mysql

还有就是为了让业务结构区分的清楚,提供给上层使用的方法的方法名首字母需要大写,

启动命令

: bootstarp.exe -- 3000

GoDemo结构

demo结构

│  bootstarp.go    
├─config
│      DBconfig.go
│      
├─controller
│      UserController.go
│      
├─mode
│      OrderEntity.go
│      UserEntity.go
│      
└─service
        OrderService.go
        UserService.go
        

启动文件

package main

import (
	_ "GoDemo/controller" 使用"_"会默认执行init方法 注册HandleFunc 调用其init函数,而不能调用其内部方法
	"fmt"
	"net/http"
	"os"
)
func main() {
	fmt.Println("listening...")
	err := http.ListenAndServe(":"+os.Args[1], nil)
	if err != nil {
		panic(err)
	}
}

DB配置文件

package config

import (
	"database/sql"
	"fmt"
	_ "github.com/go-sql-driver/mysql" //代表引入数据库驱动 
	"strings"
)

const (
	userName = "root"
	password = "password"
	ip       = "xxxx.xxxx.xxxx.xxxx"
	port     = "xxxx"
	dbName   = "database"
)
var DB *sql.DB //db指针
func init() {
	path := strings.Join([]string{userName, ":", password, "@tcp(", ip, ":", port, ")/", dbName, "?charset=utf8"}, "")
	DB, _ = sql.Open("mysql", path)
	DB.SetConnMaxLifetime(100)
	DB.SetMaxIdleConns(10)
	if err := DB.Ping(); err != nil {
		fmt.Println("connection database fail")
		return
	}
	fmt.Println("connection success")
}

控制层文件

package controller

import (
	"GoDemo/service"
	"fmt"
	"net/http"
)

func init()  {
	http.HandleFunc("/", Hello)
	http.HandleFunc("/QueryUserDetail", QueryUserDetail)
	http.HandleFunc("/upodateUser", UpodateUser)
}
func QueryUserDetail(res http.ResponseWriter, req *http.Request) {
	var user =service.Getuser();
	var order =  service.SelectOrderById(1000033905)
	fmt.Fprintln(res, "hello,"+user.Name, "今年", user.Age, order)

}
func UpodateUser(res http.ResponseWriter, req *http.Request) {
	var user = service.Updateuser(service.Getuser())
	fmt.Fprintln(res, "李茂过了2年变为", user.Age)

}
func Hello(res http.ResponseWriter, req *http.Request) {
	fmt.Fprintln(res, "Hello World")

}

业务方法处理文件

OrderService.go

    package service
    
    import (
    	"GoDemo/config"
    	"GoDemo/mode"
    	"fmt"
    )
    
    func SelectOrderById(id int) mode.Order {
    	var DB= config.DB
    	var order mode.Order
    	err := DB.QueryRow("SELECT id ,name FROM order WHERE id=?", id).Scan(&order.Id, &order.Name)
    	if err != nil {
    		fmt.Println("查询出错了")
    	}
    	return order
    
    }
UserService.go
    package service
    
    import "GoDemo/mode"
    
    func Getuser() mode.User {
    	user := mode.User{1, "李茂", 23}
    	return user
    }
    
    func Updateuser(user mode.User) mode.User {
    	user.Age = 45
    	return user
    }

业务实体文件

OrderEntity.go
    package mode
    type Order struct {
    	Id   int
    	Name string
    }
UserEntity.go
    package mode
    type User struct {
    	Id   int
    	Name string
    	Age  int
    }