Golang并发编程

go 语言的并发编程,以下是需要了解的基础知识点,也是本文主要介绍的内容. 可以对照看看这些是否已经可以熟练运用了.

  • 阻塞: 阻塞是进程(也可以是线程、协程)的状态之一(新建、就绪、运行、阻塞、终止). 指的是当数据未准备就绪,这个进程(线程、协程)一直等待,这就是阻塞.

  • 非阻塞: 当数据为准备就绪,该进程(线程、协程)不等待可以继续执行,这就是非阻塞.

  • 同步: 在发起一个调用时,在没有得到结果之前,这个调用就不返回,这个调用过程一直在等待. 这是同步.

  • 异步: 在发起调用后,就立刻返回了,这次调用过程就结束了. 等到有结果了被调用方主动通知调用者结果. 这是异步.

  • go(协程): 通过关键字 go 即可创建一个协程.

  • chan : golang 中用于并发的通道,用于协程的通信.

    • 有缓冲通道
    • 无缓冲通道
    • 单向通道
  • select: golang 提供的多路复用机制.

  • close(): golang 的内置函数, 可以关闭一个通道.

  • sync: golang 标准库之一,提供了锁.

  • 定时器: golang 标准库 time 提供的重要功能, 提供了定时器功能,可用于超时处理.

    • Timer

    • Ticker

协程_go

Go中的并发是函数相互独立运行的能力。Goroutines是并发运行的函数。Golang提供了Goroutines作为并发
处理操作的一种方式。
创建一个协程非常简单,就是在一个任务函数前面添加一个go关键字

//协程
/*
Golang中的并发是函数相互独立运行的能力。Goroutines是并发运行的函数。Golang提供了Goroutines作为并发
处理操作的一种方式。
创建一个协程非常简单,就是在一个任务函数前面添加一个go关键字
*/
package main

import (
	"fmt"
	"time"
)

func dow1()  {
	fmt.Println("开始下载Golang......")
	time.Sleep(time.Second * 1)
	fmt.Println("Golang下载完成!")
}

func dow2()  {
	fmt.Println("开始下载Python......")
	time.Sleep(time.Second * 3)
	fmt.Println("Python下载完成!")

}

func main() {
	go dow1()  //该函数开启协程
	dow2()

}

通道_channel

Go提供了一种称为通道的机制,用于在goroutine之间共享数据。当您作为goroutine执行并发活动时,需要在goroutine之间共享资源或数据,通道充当goroutine之间的管道(管道)并提供一种机制来保证同步交换。需要在声明通道时指定数据类型。我们可以共享内置、命名、结构和引用类型的值和指针。数据在通道上传递:在任何给定时间只有一个goroutine可以访问数据项:因此按照设计不会发生数据竞争。根据数据交换的行为,有两种类型的通道:无缓冲通道和缓冲通道。无缓冲通道用于执行goroutine之间的同步通信,而缓冲通道用于执行异步通信。无缓冲通道保证在发送和接收发生的瞬间执行两个goroutine之间的交换。缓冲通道没有这样的保证。

通道由make函数创建,该函数指定chan关键字和通道的元素类型。
通道的发送和接收特性
1.对于同一个通道,发送操作之间是互库的,接收操作之间也是互库的。
2.发送操作和接收操作中对元素值的处理都是不可分割的。
3.发送操作在完全完成之前会被阻塞。接收操作也是如此。
package main

import (
	"fmt"
	"time"
)

//定义一个无缓冲区的通道
var Block = make(chan int)
//定义一个有缓冲区的通道
var Block_512 = make(chan int ,512)

func Sed() {
	v := 30
	fmt.Println("发生值 V:", v)
	time.Sleep(time.Second * 2)
	Block <- v		//向无缓存区存存入数据
}

func test()  {
	i := 1
	for i <= 5 {
		println(i)
		Block_512 <- i	////向有缓存区存存入数据
		i++
	}
}

func test2()  {
	i := 1
	for i <= 5 {
		v := <- Block_512	//向有缓冲区取数据
		println("V:",v)
	}

}



func main() {
	go test()
	go Sed()
	go test2()
	defer close(Block)
	fmt.Println("等待接收:")
	v := <-Block			//向无缓存区取数据
	fmt.Println("接收值 V:", v)
	fmt.Println("END")
}

主进程等待_WaitGroup

作用于主进程等待协程执行完在执行

package main

import (
	"fmt"
	"sync"
)

var Wg sync.WaitGroup   //定义一个 WaitGroup

func Work(i int)  {
	fmt.Println("Hello",i)
	defer Wg.Done()		//协程结束时调用WaitGroup.Done() 删除任务
}

func main()  {
	i := 0
	for i<=10 {
		Wg.Add(1)		//协程开始前在WaitGroup+1
		go Work(i)

		i++
	}

	Wg.Wait()				//阻塞等待直到WaitGroup进程为0  ,可以注释该行查看效果
	fmt.Println("END")
}

进程调度_runtime

runtime包里面定义了一些协程管理相关的api

runtime.Goexit() 结束进程
runtime.Gosched() 让出CPU时间片,让其他进程优先执行
runtime.NumCPU() 获取CPU核心数
runtime.GOMAXPROCS() 设置程序使用CPU最大核心数
package main

import (
	"fmt"
	"runtime"
)

func show(s string) {
	i := 1
	for i <= 10 {
		fmt.Println("Show:",s)
		i++
	}
}

func show2(s string) {
	////测试runtime.Goexit()	结束进程
	i := 1
	for i <= 10 {
		if i > 5{
			runtime.Goexit()		//该进程退出
		}
		fmt.Println("Show:",s)
		i++
	}
}



func test1() {
	//测试runtime.Gosched()	让出CPU时间片,让其他进程优先执行
	go show("Golang")		//开启一个协程
	go show2("Python")
	runtime.Gosched()		//等待其他进程执行,可以注释掉这行看效果
	i := 1
	for i <= 10 {
		fmt.Println("Show: test1()", )

		i++
	}
}

func main() {
	max := runtime.NumCPU()			//使用runtime.NumCPU() 获取CPU核心数
	fmt.Println("CPU核心数:",max)
	runtime.GOMAXPROCS(2)		//使用runtime.GOMAXPROCS() 设置程序使用CPU最大核心数
	test1()
}

互斥锁实现同步_Mutex

除了使用channel实现同步之外,还可以使用Mutex互斥锁的方式实现同步。

package main

//互斥锁 Mutex

import (
   "fmt"
   "sync"
)

var lock sync.Mutex          //创建Mutex  互斥锁
var Wg sync.WaitGroup     //创建WaitGroup进程队列
var i int = 100

func add() {
   lock.Lock()    //进程开始前锁定
   i++
   fmt.Println("add:", i)
   lock.Unlock()  //进程结束后解锁
   Wg.Done()     //进程队列减一
}

func sub() {
   lock.Lock()
   i--
   fmt.Println("sub:", i)
   lock.Unlock()
   Wg.Done()     //进程队列减一
}

func main() {
   for a := 1; a <= 100; a++ {
      Wg.Add(1)
      go add()   //进程队列加一
      Wg.Add(1)
      go sub()
   }
   Wg.Wait()     //等待进程队列运行完成
   fmt.Println("END  :", i)
}

channel遍历

package main

import "fmt"

var block = make(chan int)


func f2()  {
	for i := 1;i<=10;i++ {
		block <- i
	}
	defer close(block)			//写入完成后关闭通道
}


func main() {
	go f2()

	for i := range block{		//遍历通道
		fmt.Println(i)
	}

}

Ticker_周期循环执行

Timer只执行一次
Ticker可以周期

package main

import (
	"fmt"
	"time"
)

func main() {
	tk := time.NewTicker(time.Second * 2)  //创建一个Ticker,执行周期为2秒

	goo := 1

	for _ = range tk.C {
		fmt.Println(time.Now())
		fmt.Println("RUN.....")
		goo++
		if goo > 5{
			tk.Stop()		//设置停止条件
			break
		}
	}
}

原子变量

解决并发操作数据脏读的问题

package main

import (
	"sync/atomic"
	"time"
)

var i int64 = 100

func add()  {
	//i++						//使用传统方式需上锁,否则结果不一样
	atomic.AddInt64(&i,1)		//使用原子变量则可以解决
}

func sub()  {
	//i--
	atomic.AddInt64(&i,-1)
}

func main() {
	for i := 1; i <= 1000;i++{
		go add()
		go sub()
	}

	time.Sleep(time.Second*2)
	println(i)
}

其他原子操作

package main

import "sync/atomic"

func main() {
	var  i int64 = 1000

	atomic.LoadInt64(&i)	//读取
	println(i)

	atomic.StoreInt64(&i,2000)		//修改
	println(i)

	//比较,先比较原值,在进行修改,修改成功返回true
	b := atomic.CompareAndSwapInt64(&i, 2000, 4000)	//旧值 新值 返回一个bool对象

	println(i,b)	//4000 true
}