Golang高并发服务器开发学习(附源码)

1.并发编程与协程Goroutine

1.1并行与并发

并行:在同一时刻,有多条指令在多个处理器上同时执行,借助多核cpu实现

并发:宏观:用户体验上,程序在并行执行

微观:多个计划任务,顺序执行。在飞快的切换,轮换使用cpu时间轮片

时间轮片:

1.2常见并发编程技术

进程并发:

程序:编译成功得到的二进制文件。 (占用磁盘空间) (死的) 1

进程:运行起来的程序,占用系统资源。 (内存) (活的) N

进程状态:初始态、就绪态、运行态、挂起(阻塞态)、终止(停止态)

线程并发:

线程:LWP 轻量级的进程 最小的执行单位--cpu分配时间轮片的对象

进程:最小的系统资源分配单位,给线程提供执行环境

线程同步:指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时其它线程为保证数据一 致性,不能调用该功能。

同步:协同步调,规划先后顺序

线程同步机制:互斥锁(互斥量):建议锁,拿到锁以后,才能访问数据,没有拿到锁的线程,阻塞等待

读写锁:一把锁(读属性、写属性),写独占,读共享,写锁优先级高

协程并发:

协程:coroutine,轻量级线程,提高程序执行的效率

总结:进程(稳定性强)、线程(节省资源)、协程(效率高)都可以完成并发。

老板--手机

生产线--设备、材料、厂房--进程(资源分配单位)

工人--线程 --单进程、单线程的程序

50工人--50线程 --单进程、多线程的程序

10条生产线--500工人 --多进程、多线程的程序

利用闲暇时间义务板砖 --协程

1.3Goroutine

1.3.1Goroutine概念,创建及特性

概念:go程,创建于进程中,直接使用go关键,放置于函数调用前面,产生一个go程,实现并发

package main
import (
    "fmt"
    "time"
)

func sing() {
    for i := 0; i < 5; i++ {
        fmt.Println("唱--爱是一本书")
        time.Sleep(100 * time.Millisecond)
    }
}

func dance() {
    for i := 0; i < 5; i++ {
        fmt.Println("跳--跳一跳")
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
    go sing()
    go dance()
    for {
       ; 
    }
}

实例:

package main
import (
    "fmt"
    "time"
)

func newTask() {
    i := 0
    for {
        i++
        fmt.Printf("new goroutine:i=%d\n", i)
        time.Sleep(1 * time.Second) //延时1s
    }
}

func main() {
    //创建一个goroutine,启动另外一个任务
    go newTask()
    i := 0
    //main goroutine 循环打印
    for {
        i++
        fmt.Printf("main goroutine:i=%d\n", i)
        time.Sleep(1 * time.Second) //延时1s
    }
}

特性:主go程结束,子go程随之退出。

package main
import (
    "fmt"
    "time"
)

func main() {
    //创建一个子go程
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println("-----I'm goroutine-----")
            time.Sleep(1 * time.Second)
        }
    }()

    //主go程
    for i := 0; i < 10; i++ {
        fmt.Println("-----I'm main-----")
        time.Sleep(1 * time.Second)
        if i == 2 {
            break
        }
    }
}

1.3.2runtime包

runtime.Gosched():出让当前go程所占用的cpu时间片,再次获得cpu时,从出让位置继续执行

package main
import (
    "fmt"
    "runtime"
)

func main() {
    go func() {
        for {
            fmt.Println("this is goroutine test ")
        }
    }()

    for {
        runtime.Gosched() //出让当前cpu时间片
        fmt.Println("this is main test")
    }
}

runtime.Goexit():结束调用该函数的当前go程。Goexit0):之前注册的 defer都生效。

return:返回当前函数调用到调用者那里去。retumn之前的 defer 注册生效。

package main

import (
    "fmt"
    "runtime"
)

func test() {
    defer fmt.Println("cccccccc")
    //return
    runtime.Goexit() //退出当前go程
    fmt.Println("ddddddd")
}
func main() {
    go func() {
        defer fmt.Println("aaaaaaaa")
        test()
        fmt.Println("bbbbbbb")
    }()

    for {
        ;
    }
}

runtime.GOMAXPROCS():设置当前进程使用的最大cpu核数,返回上一次调用成功的设置值,首次返回默认值

package main
import (
    "fmt"
    "runtime"
)

func main() {
    n := runtime.GOMAXPROCS(1) //单核
    fmt.Println("n=", n)

    n = runtime.GOMAXPROCS(2) //双核
    fmt.Println("n=", n)

    for i := 0; i < 10; i++ {
        go fmt.Println(0) //子go程
        fmt.Println(1)    //主go程
    }
}

2.协程间通信与Channel

2.1channel

概念:是一种数据类型,对应一个“管道”主要用来解决go程的同步问题以及协程之间数据共享(数据传递)的问题。通过通信来共享内存,而不是共享内存来通信

定义:make(chan 在channel中传递的数据类型,容量) 容量=0:无缓冲channel || 容量>0:有缓存channel

【补充知识】:每当有一个进程启动时,系统会自动打开三个文件:标准输入(stdin)、标准输出(stdout)、标准错误(stderr),当进程运行结束,操作系统自动关闭三个文件

channel有两个端:

一端:写端(传入端) chan <-

另一端:读端(传出端) <- chan

要求:读端和写端必须同时满足条件,才在channel上进行数据流动,否则,则阻塞

package main
import (
    "fmt"
    "time"
)

// 全局定义channel,用来完成数据同步
var channel = make(chan int)

// 定义一台打印机
func Printer(s string) {
    for _, ch := range s {
        fmt.Printf("%c", ch) //屏幕:stdout
        time.Sleep(300 * time.Millisecond)
    }
}

// 定义两个人使用打印机
func person1() { //先执行
    Printer("hello")
    channel <- 1
}
func person2() { //后执行
    <-channel
    Printer("world")
}
func main() {
    go person1()
    go person2()
    for {
        ;
    }
}

channel同步,数据传递:

package main
import "fmt"

func main() {
    ch := make(chan string) //无缓冲channel
    //len(ch):channel中剩余未读取数据个数,cap(ch):channel容量
    fmt.Println("len=", len(ch), "cap=", cap(ch))
    go func() {
        for i := 0; i < 5; i++ {
            fmt.Println(i)
        }
        //通知主go打印完成
        ch <- "done"
    }()
    fmt.Println(<-ch)
}

无缓冲channel(同步通信):通道容量为0,len=0,应用于两个go程中,一个读另一个写,具备同步的能力(打电话)

有缓冲channel(异步通信):通道容量为0,应用于两个go程中,一个读另一个写,缓冲区可以进行数据存储,存储至容量上限,阻塞。具备异步能力,不需同时操作缓冲区(发短信)

package main
import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan int, 5) //存满3个元素之前,不会阻塞
	fmt.Println("len=", len(ch), "cap=", cap(ch))
	go func() {
		for i := 0; i < 8; i++ {
			ch <- i
			fmt.Println("子go程", i, "len=", len(ch), "cap=", cap(ch))
		}
	}()
	time.Sleep(3 * time.Second)
	for i := 0; i < 8; i++ {
		num := <-ch
		fmt.Println("主go程读到", num)
	}
}

关闭channel:

确定不再对端发送、传输数据,使用close(ch)关闭channel

对端可以判断channel是否关闭:

if num,ok := <-ch; ok == true{

如果对端已经关闭, ok --> false . num无数据

如果对端没有关闭,ok--> true . num保存读到的数据

可以使用range替代ok

总结:1.数据不发送完不应该关闭

2.已经关闭的channel,不能再向其写入数据,可以从中读取数据,无缓冲(读到0)

有缓冲(缓冲区内有数据,先读数据,读完数据后,可以继续读,读到0)

package main
import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            ch <- i
        }
        close(ch) //写端,写完数据主动关闭channel
        fmt.Println("读端关闭")
    }()
    time.Sleep(2 * time.Second)
    //for {
    // if num, ok := <-ch; ok == true {
    //    fmt.Println("读到数据:", num)
    // } else {
    //    n := <-ch
    //    fmt.Println("关闭后:", n)
    //    break
    // }
    //}
    for num := range ch {
        fmt.Println("读到数据:", num)
    }
}

单向channel:

默认的channel是双向的。 var ch chan int ch = make(chan int)

单向写channel:var sendCh chan <- int sendCh = make(chan <- int) 不能读操作

单向读channel:var recvCh <- chan int recvCh = make(<- chan int)

转换:

双向channel可以隐式转换为任意一种单项channel

sendCh = ch

单向channel不能转换为双向channel

ch = sendCh/recvCh error!!

传参:传【引用】

package main
import "fmt"

func send(out chan<- int) {
    out <- 1
    close(out)
}
func recv(in <-chan int) {
    n := <-in
    fmt.Println(n)
}

func main() {
    ch := make(chan int) //双向channel
    go func() {
        send(ch) //双向channel转为写入channel
    }()
    recv(ch)
}

2.2单向channel及应用

生产者:发送数据端

消费者:接收数据端

缓冲区:1.解耦(降低生产者和消费者之间耦合度)

2.并发(生产者消费者数量不对等时,能保持正常通信)

3.缓存(生产者和消费者数据处理速度不一致时,暂存数据)

package main
import (
    "fmt"
    "time"
)

func Producer(out chan<- int) {
    for i := 0; i < 10; i++ {
        fmt.Println("生产者生产:", i)
        out <- i * i
    }
    close(out)
}
func Consumer(in <-chan int) {
    for num := range in {
        fmt.Println("消费者拿到:", num)
        time.Sleep(1 * time.Second)
    }
}
func main() {
    ch := make(chan int)
    go Producer(ch) //子go程 生产者
    Consumer(ch)    //主go程 消费者
}

订单模拟:

package main

import "fmt"

type OrderInfo struct {
	id int
}

func Producer(out chan<- OrderInfo) {
	for i := 0; i < 10; i++ {
		order := OrderInfo{id: i}
		out <- order
	}
	close(out)
}

func Consumer(in <-chan OrderInfo) {
	for order := range in {
		fmt.Println("订单id为:", order.id)
	}
}

func main() {
	ch := make(chan OrderInfo)
	go Producer(ch)
	Consumer(ch)
}

2.3定时器

2.3.1time.Timer

time.Timer:创建定时器,指定定时时长,定时到达后。系统会自动向定时器的成员C写系统当前时间。(对 chan 的写操作)

type Time struct{
    C <- chan Time
    r runtime Timer
}

读取 Timer.C 得到 定时后的系统时间。并且完成一次 chan 的 读操作

package main
import (
    "fmt"
    "time"
)

func main() {
    fmt.Println("当前时间:", time.Now())
    //创建定时器
    myTimer := time.NewTimer(time.Second * 2)
    nowTime := <-myTimer.C //chan类型
    fmt.Println("现在时间:", nowTime)
}

三种定时方法:

package main
import (
    "fmt"
    "time"
)

// 三种定时方法
func main() {
    //1.sleep
    time.Sleep(time.Second)
    //2.Timer.C
    myTimer := time.NewTimer(time.Second * 2) //创建一个定时器,指定定时时长
    nowTime := <-myTimer.C                    //定时满,系统自动写入系统时间
    fmt.Println("现在时间:", nowTime)
    //3.time.After
    nowTime2 := <- time.After(time.Second)
    fmt.Println("现在时间:", nowTime2)
}

定时器的停止和重置:

package main
import (
    "fmt"
    "time"
)

func main() {
    myTimer := time.NewTimer(time.Second * 3) //创建定时器
    myTimer.Reset(time.Second * 1)            //重置定时时长为1
    go func() {
        <-myTimer.C
        fmt.Println("子go程定时完毕")
    }()
    myTimer.Stop() //设置定时器停止 <-myTimer.C会阻塞
    for {
        ;
    }
}

2.3.2time.Ticker

time.Ticker:定时时长到达后,系统会自动向 Ticker的 C中写入系统当前时间。并且,每隔一个定时时长后,循环写入系统当前时间。在子go程中循环读取 C,获取系统写入的时间,

type Ticker struct{
    C <- chan Time
    r runtime Timer
}

周期定时:

package main
import (
    "fmt"
    "time"
)

func main() {
    quit:= make(chan bool) //创建一个判断是否终止的channel

    fmt.Println("now:", time.Now())
    myTicker := time.NewTicker(time.Second * 1)

    i:=0
    go func() {
        for {
            nowTime := <-myTicker.C
            i++
            fmt.Println("nowTime:", nowTime)
            if i==3{
                quit <- true //解除主go程阻塞
            }
            break//return//runtime.Goexit()
        }
    }()
    <-quit //子go程循环获取<-myTicker.C期间,一直阻塞
}

3.并发编程与同步机制

3.1select

select监听channel通信:

package main
import (
    "fmt"
    "runtime"
    "time"
)

func main() {
    ch := make(chan int)    //用来进行数据通信的channel
    quit := make(chan bool) //用来判断是否退出的channel

    go func() { //写数据
        for i := 0; i < 5; i++ {
            ch <- i
            time.Sleep(1 * time.Second)
        }
        close(ch)
        quit <- true //通知主go程退出
        runtime.Goexit()
    }()

    for { //主go程,读数据
        select {
            case num := <-ch:
            fmt.Println("读到:", num)
            case <-quit:
            return //终止进程
        }
        fmt.Println("=========")
    }
}

select实现斐波那契数列:

package main
import (
    "fmt"
    "runtime"
)

func fibonacci(ch <-chan int, quit <-chan bool) {
    for {
        select {
            case n := <-ch:
            fmt.Print(n, " ")
            case <-quit:
            runtime.Goexit()
        }
    }
}

func main() {
    ch := make(chan int)
    quit := make(chan bool)
    go fibonacci(ch, quit) //子go程 打印fibonacci数列
    x, y := 1, 1
    for i := 0; i < 20; i++ {
        ch <- x
        x, y = y, x+y
    }
    quit <- true
}

超时处理:

package main
import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan int)
    quit := make(chan bool)

    go func() { //子go程获取数据
        for {
            select {
                case v := <-ch:
                fmt.Println("Received:", v)
                case <-time.After(3 * time.Second):
                quit <- true
                goto lable
            }
        }
        lable:
        fmt.Println("Exit")
    }()
    for i := 0; i < 2; i++ {
        ch <- i
        time.Sleep(2 * time.Second)
    }
    <-quit //主go程,阻塞等待 子go程通知,退出
    fmt.Println("Done")
}

3.2锁和条件变量

3.2.1锁

死锁:不是一种锁,是一种错误使用锁导致的现象

1.单go程自己死锁:channel应该在最少两个以上go程中进行通信

2.go程间channel访问顺序导致死锁:使用channel一端读(写),要保证另一端写(读)操作同时有机会执行

3.多go程,多channel交叉死锁:Ago程:掌握M,尝试拿N Bgo程:掌握N,尝试拿M

4.在go语言中,尽量不要将互斥锁、读写锁与channel混用--隐性死锁

package main

func main() {
    //死锁1
    //ch := make(chan int)
    //ch <- 789
    //num := <-ch
    //fmt.Println(num)

    //死锁2
    //ch := make(chan int)
    //num := <-ch
    //fmt.Println(num)
    //go func() {
    // ch <- 789
    //}()

    //死锁3
    // ch1 := make(chan int)
    // ch2 := make(chan int)
    // go func() {
    //     for {
    //         select {
    //             case num := <-ch1:
    //             ch2 <- num
    //         }
    //     }
    // }()

    // for {
    //     select {
    //         case num := <-ch2:
    //         ch1
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值