参考文章:
Go系列文章 :https://www.cnblogs.com/wdliu/category/1233750.html
Go 语言中文网系列教程: https://studygolang.com/subject/2
调用函数或者方法时,在前面加上关键字 go,可以让一个新的 Go 协程并发地运行。
启动一个新的协程时,协程的调用会立即返回,如果希望运行其他 Go 协程,Go 主协程必须继续运行着。
package main
import (
"fmt"
"time"
)
func hello() {
fmt.Println("Hello world goroutine")
}
func main() {
go hello()
time.Sleep(1 * time.Second)
fmt.Println("main function")
}
并发示例:
func main() {
for i := 0; i < 10; i++ {
go func(i int) {
//time.Sleep(time.Second * 1)
fmt.Println(i)
}(i)
}
time.Sleep(time.Millisecond * 50)
}
以多个窗口并发卖票为例:
package main
import "fmt"
import "time"
import "math/rand"
import "runtime"
var total_tickets int32 = 10;
func sell_tickets(i int){
for{
if total_tickets > 0 { //如果有票就卖
time.Sleep( time.Duration(rand.Intn(5)) * time.Millisecond)
total_tickets-- //卖一张票
fmt.Println("id:", i, " ticket:", total_tickets)
}else{
break
}
}
}
func main() {
runtime.GOMAXPROCS(4) //我的电脑是4核处理器,所以我设置了4
rand.Seed(time.Now().Unix()) //生成随机种子
for i := 0; i < 5; i++ { //并发5个goroutine来卖票
go sell_tickets(i)
}
//等待线程执行完
var input string
fmt.Scanln(&input)
fmt.Println(total_tickets, "done") //退出时打印还有多少票
}
可能的输出为:
id: 1 ticket: 9
id: 0 ticket: 8
id: 3 ticket: 7
id: 4 ticket: 6
id: 1 ticket: 5
id: 1 ticket: 4
id: 2 ticket: 3
id: 1 ticket: 2
id: 0 ticket: 1
id: 4 ticket: 0
id: 1 ticket: -1
id: 3 ticket: -2
id: 2 ticket: -3
id: 0 ticket: -4
可见,我们需要使用上锁,我们可以使用互斥量来解决这个问题。
即在共享资源读写前添加互斥锁:
func sell_tickets(i int){
for total_tickets > 0 {
mutex.Lock()
if total_tickets > 0 { //如果有票就卖
time.Sleep( time.Duration(rand.Intn(5)) * time.Millisecond)
total_tickets-- //卖一张票
fmt.Println("id:", i, " ticket:", total_tickets)
}
mutex.Unlock()
}
}
package main
import "fmt"
import "time"
import "sync/atomic"
func main() {
var cnt uint32 = 0
for i := 0; i < 10; i++ {
go func() {
for i:=0; i<200; i++ {
time.Sleep(time.Millisecond)
atomic.AddUint32(&cnt, 1)
}
}()
}
time.Sleep(time.Second)//等一秒钟等goroutine完成
cntFinal := atomic.LoadUint32(&cnt)//取数据
fmt.Println("cnt:", cntFinal)
}
通过信道来实现 Go 协程间的通信,发送与接收信道默认是阻塞的。
channel和切片有点类似,默认是nil,引用类型。
常见操作:
//创建
ch := make(chan int)
//读写
// write to channel
ch <- x
// read from channel
x <- ch
//关闭
close(ch)
示例代码:
package main
import (
"fmt"
)
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")
}
上面的chan是无缓冲的,常用来作为同步,有无缓冲的示例图对比:
这里参考文章:由浅入深学channel 和Golang实现经典并发模型:生产者消费者
这个代码的有点类似Unix C中信号量的思想:
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
type Product struct {
prod_num int //生产该产品的生产者序号
value int
}
//采用通道实现产品双向传递,缓冲区设为10
//通道的运作方式替代了伪代码中 empty 和 full 信号量的作用
//通道的另一个特性:任意时刻只能有一个协程能对 channel 中某一个item进行访问。替代了伪代码中 mutex 信号量的作用
var ch = make(chan Product, 10)
//终止生产者生产的信号,否则无限执行下去
var stop = false
func Producer(wg *sync.WaitGroup, p_num int) {
for !stop { //不断生成,直到主线程执行到终止信号
p := Product{prod_num: p_num, value: rand.Int()}
ch <- p
fmt.Printf("producer %v produce a product: %#v\n", p_num, p)
time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond) //延长执行时间
}
wg.Done()
}
func Consumer(wg *sync.WaitGroup, c_num int) {
//通道里没有产品了就停止消费
for p := range ch {
fmt.Printf("consumer %v consume a product: %#v\n", c_num, p)
time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
}
wg.Done()
}
func main() { //主线程
var wgp sync.WaitGroup
var wgc sync.WaitGroup
wgp.Add(2)
wgc.Add(2)
//设5个生产者、5个消费者
for i := 0; i < 2; i++ {
go Producer(&wgp, i)
go Consumer(&wgc, i)
}
time.Sleep(time.Duration(1) * time.Second)
stop = true
wgp.Wait()
close(ch)
wgc.Wait()
}
Go中的timer只触发一次,设计时需注意:
package main
import "time"
import "fmt"
func main() {
ticker := time.NewTicker(time.Second)
go func () {
for t := range ticker.C {
fmt.Println(t)
}
}()
//设置一个timer,10钞后停掉ticker
timer := time.NewTimer(10*time.Second)
<- timer.C
ticker.Stop()
fmt.Println("timer expired!")
}
coolshell上给的demo非常经典,已测试
服务器server.go
package main
import (
"net"
"fmt"
"io"
)
const RECV_BUF_LEN = 1024
func main() {
listener, err := net.Listen("tcp", "0.0.0.0:6666")//侦听在6666端口
if err != nil {
panic("error listening:"+err.Error())
}
fmt.Println("Starting the server")
for {
conn, err := listener.Accept() //接受连接
if err != nil {
panic("Error accept:"+err.Error())
}
fmt.Println("Accepted the Connection :", conn.RemoteAddr())
go EchoServer(conn)
}
}
func EchoServer(conn net.Conn) {
buf := make([]byte, RECV_BUF_LEN)
defer conn.Close()
for {
n, err := conn.Read(buf);
switch err {
case nil:
conn.Write( buf[0:n] )
case io.EOF:
fmt.Printf("Warning: End of data: %s \n", err);
return
default:
fmt.Printf("Error: Reading data : %s \n", err);
return
}
}
}
客户端client.go
package main
import (
"fmt"
"time"
"net"
)
const RECV_BUF_LEN = 1024
func main() {
conn,err := net.Dial("tcp", "127.0.0.1:6666")
if err != nil {
panic(err.Error())
}
defer conn.Close()
buf := make([]byte, RECV_BUF_LEN)
for i := 0; i < 5; i++ {
//准备要发送的字符串
msg := fmt.Sprintf("Hello World, %03d", i)
n, err := conn.Write([]byte(msg))
if err != nil {
println("Write Buffer Error:", err.Error())
break
}
fmt.Println("send:", msg)
//从服务器端收字符串
n, err = conn.Read(buf)
if err !=nil {
println("Read Buffer Error:", err.Error())
break
}
fmt.Println("recv:", string(buf[0:n]))
//等一秒钟
time.Sleep(time.Second)
}
}
输出:
go run server.go
Starting the server
Accepted the Connection : 127.0.0.1:10834
Warning: End of data: EOF
Accepted the Connection : 127.0.0.1:10866
Warning: End of data: EOF
---
go run client.go
send: Hello World, 000
recv: Hello World, 000
send: Hello World, 001
recv: Hello World, 001
type Person struct {
Name string
age int
Hobbies []string
}
如果是类似上述结构体,那么编码时age对应的字段是不会被编码的,究其原因:
只要是可导出成员(变量首字母大写),都可以转成json。因成员变量sex是不可导出的,故无法转成json。
如果变量打上了json标签,如Name旁边的json:"name"
,那么转化成的json key就用该标签“name”
因此:
```go
package main
import (
"fmt"
"log"
"encoding/json"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Hobbies []string `json:"hobbies"`
}
func main() {
hobbies := []string{"book","film"}
p := Person{
Name : "paopao",
Age : 22,
Hobbies : hobbies,
}
fmt.Printf("%T, %+v\n",p, p)
//转换为字节切片
jsonByteData, err := json.Marshal(p)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%T\n", jsonByteData)
//转化为string
jsonStringData := string(jsonByteData)
fmt.Printf("%T, %s\n", jsonStringData, jsonStringData)
//解码
var newPerson Person
err = json.Unmarshal([]byte(jsonStringData),&newPerson)
if err !=nil{
fmt.Println("err",err)
}
fmt.Printf("%T ", newPerson)
fmt.Println(newPerson)
}