Go_并发编程

1 并发编程

1.1 并发与并行

并发:单位时间内,程序可以处理的能力。
并行:同一时刻,程序可以处理的能力。

1.2 为什么需要并发
  1. 不阻塞等待其他任务的执行,节省cpu时间,提高系统新能。
  2. 并行可以使系统变得简单,将复杂的大任务切成许多小任务,单独测试
    在开发中,经常会遇到为什么某些进程通常会相互等待呢?为什么有些运行慢,有些快呢?
    通常受限来源于进程I/O或CPU。
    进程I/O限制
    如:等待网络或磁盘访问
    CPU限制
    如:大量计算
1.3 Go的并发原语
1.3.1 协程Goroutines

每个go程序至少都有一个Goroutine: 主Goroutine(在运行进程时自动创建)。以及程序中其他Goroutine

func main() {
	go func() {
		fmt.Println("you forgot me !")
	}()
}

在go中有个package是sync,里面包含了:

WaitGroup、Mutex、Cond、Once、Pool,下面依次介绍。
1. WaitGroup
假设主线程要等待其余的groutine都运行完毕,不得不在末尾添加time.Sleep();可以使用WaitGroup:
Add(n)把计数器设置为n,Done()会将计数器每次减1,Wait()函数会阻塞代码运行,直到计数器减0。

	//等待组必须通过指针传递给函数
	func worker(id int, wg *sync.WaitGroup){
		defer wg.Done()
		fmt.Printf("Worker %d starting\n", id)
		time.Sleep(time.Second)
		fmt.Printf("Worker %d done\n", id)
	}
func main() {

	var wg sync.WaitGroup

	for i := 1; i <= 5; i++ {
		wg.Add(1)
		go worker(i, &wg)
	}

	wg.Wait()
}

2. Once
sync.Once 可以控制函数只能被调用一次,不能多次重复调用

var doOnce sync.Once

func main() {
	DoSomething()
	DoSomething()
}

func DoSomething() {
	doOnce.Do(func() {
		fmt.Println("Run once - first time, loading...")
	})
	fmt.Println("Run this every time")
}

//输出
Run once - first time, loading...
Run this every time
Run this every time

3. 互斥锁Mutex
互斥锁是并发程序对共享资源进行访问控制的主要手段,在go中的sync中提供了Mutex的支持。
例如:使用互斥锁解决多个Goroutine访问同一变量。

// SafeCounter 的并发使用是安全的。
type SafeCounter struct {
	v   map[string]int
	mux sync.Mutex
}

func (c *SafeCounter) Inc(key string) {
  c.mux.Lock()
  defer c.mux.Unlock()
	// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
  c.v[key]++
}

// Value 返回给定 key 的计数器的当前值。
func (c *SafeCounter) Value(key string) int {
	c.mux.Lock()
	// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
	defer c.mux.Unlock()
	return c.v[key]
}

func main() {
	c := SafeCounter{v: make(map[string]int)}
	for i := 0; i < 1000; i++ {
		go c.Inc("somekey")
	}

	time.Sleep(time.Second)
	fmt.Println(c.Value("somekey"))
}

4.读写锁

// SafeCounter 的并发使用是安全的。
type SafeCounter struct {
	v     map[string]int
	rwmux sync.RWMutex
}

// Inc 增加给定 key 的计数器的值。
func (c *SafeCounter) Inc(key string) {
	// 写操作使用写锁
	c.rwmux.Lock()
	defer c.rwmux.Unlock()
	// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
	c.v[key]++
}

// Value 返回给定 key 的计数器的当前值。
func (c *SafeCounter) Value(key string) int {
  // 读的时候加读锁
	c.rwmux.RLock()
	// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
	defer c.rwmux.RUnlock()
	return c.v[key]
}

func main() {
	c := SafeCounter{v: make(map[string]int)}
	for i := 0; i < 1000; i++ {
		go c.Inc("somekey")
	}

	time.Sleep(time.Second)

	for i := 0; i < 10; i++ {
		fmt.Println(c.Value("somekey"))
	}
}

5.条件变量 Cond
sync.Cond 是条件变量,它可以让一系列的Goroutine都在满足特定条件时被唤醒。
条件变量通常与互斥锁一起使用,条件变量可以在共享资源的状态变化时通知相关协程。

//创建一个Cond的条件变量
func NewCond(l Locker) *Cond

//广播通知,调用时可以加锁,也可以不加
func (c *Cond) Broadcast()
//单播通知,只唤醒一个等待c的goroutine
func (c *Cond) Signal()
//Wait 等待通知, Wait()会自动释放c.L,并挂起调用者的goroutine。之后恢复执行,Wait()会在返回时对c.L加锁。除非被Signal或者Broadcast唤醒,否则Wait()不会返回。
func (c *Cond) Wait()

6.原子操作
原子操作即是进行过程中不能被中断的操作。针对某个值的原子操作在被进行的过程中,CPU绝不会再去进行其他的针对该值的操作。 为了实现这样的严谨性,原子操作仅会由一个独立的CPU指令代表和完成。

在sync/atomic 中,提供了一些原子操作,包括加法(Add)、比较并交换(Compare And Swap,简称 CAS)、加载(Load)、存储(Store)和交换(Swap)

//加减
	var i int64
	atomic.AddInt64(&i ,1)
	ffmt.Println("i = i + 1 =", i)
	atomic.AddInt64(&i, -1)
	fmt.Println("i = i - 1 =", i
//cas
//如果addr和old相同,就用new代替addr。
//func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)

var a int32 = 1
var b int32 = 2
var c int32 = 3
ok := atomic.CompareAndSwapInt32(&a, a, b)
fmt.Printf("ok = %v, a = %v, b = %v\n", ok, a, b)
ok = atomic.CompareAndSwapInt32(&a, c, b)
fmt.Printf("ok = %v, a = %v, b = %v, c=%v\n", ok, a, b, c)

//强制交换
var x int32 = 1
var y int32 = 2
old := atomic.SwapInt32(&x, y)
fmt.Println(x, old)

//加载当读取该指针指向的值时,CPU 不会执行任何其它针对此值的读写操作
var x1 int32 = 1
y1 := atomic.LoadInt32(&x)
fmt.Println("x1, y1:", x1, y1)

//存储加载逆向操作
var xx int32 = 1
var yy int32 = 2
atomic.StoreInt32(&yy, atomic.LoadInt32(&xx))
fmt.Println(xx, yy)

//原子类型
v := atomic.Value{}
v.Store(1)
fmt.Println(v.Load())

//临时对象池Pool
ync.Pool 可以作为临时对象的保存和复用的集合

P是Goroutine中的重要组成之一,例如:P实际上在操作时会为它的每一个goroutine相关的P生成一个本地P。 本地池没有,则会从其它的 P 本地池获取,或者全局P取。

sync.Pool对于需要重复分配、回收内存的地方,sync.Pool 是一个很好的选择。减少GC负担,如果Pool中有对象,下次直接取,不断服用对象内存,减轻 GC 的压力,提升系统的性能。

var pool *sync.Pool

type Foo struct {
	Name string
}

func Init() {
	pool = &sync.Pool{
		New: func() interface{} {
			return new(Foo)
		},
	}
}

func main() {
	fmt.Println("Init p")
	Init()

	p := pool.Get().(*Foo)
	fmt.Println("第一次取:", p)
	p.Name = "bob"
	pool.Put(p)

	fmt.Println("池子有对象了,调用获取", pool.Get().(*Foo))
	fmt.Println("池子空了", pool.Get().(*Foo))
}
1.3.2 通道Channel

实体之间通过发送消息进行通信,这里发送消息时使用的就是通道,或者叫 Channel。Goroutine对应并发实体。

//使用make创建
unBufferChan := make(chan int) 
bufferChan := make(chan int, x) 

//读写
ch := make(chan int, 1)

// 读操作
x <- ch

// 写操作
ch <- x

//最终要关闭
close(ch)

无缓冲的Channel
发送与接受同时进行。如果没有Goroutine读取Channel(<-Channel),发送者(Channel<-x)会一直阻塞。

有缓冲的Channel
发送与接受并非同时进行。当队列为空,接受者阻塞;队列满,发送者阻塞。

Select

  • 每个case 都必须是一个通信
  • 所有channel表达式都会被求值
  • 如果没有default语句,select将阻塞,直到某个通信可以运行
  • 如果多个case都可以运行,select会随机选择一个执行
//select特性之一:随机选择,下面会随机打印不同的case结果。 例如:
ch := make(chan int, 1)
ch <- 1
select {
case <-ch:
	fmt.Println("ch 1")
case <-ch:
	fmt.Println("ch 2")
default:
	fmt.Println("ch default")
}
//假设chan中没有值,有可能引发死锁。必须要加上default避免
ch := make(chan int, 1)
select {
case <-ch:
	fmt.Println("ch 1")
case <-ch:
	fmt.Println("ch 2")
}
default:
	fmt.Println("ch default")

//设置超时
timeout := make(chan bool, 1)
go func() {
	time.Sleep(2 * time.Second)
	timeout <- true
}()
ch := make(chan int, 1)

select {
case <-ch:
	fmt.Println("ch 1")
case <-timeout:
	fmt.Println("timeout 1")
case <-time.After(time.Second * 1):
	fmt.Println("timeout 2")
}

//select+defaul方式来确保channel是否满
//如果要调整channel大小,可以在make的时候改变size,这样就可以在case中往channel继续写数据。
ch := make(chan int, 1)
ch <- 1
select {
case ch <- 1:
	fmt.Println("channel value is ", <-ch)
	fmt.Println("channel value is ", <-ch)
default:
	fmt.Println("channel blocking")
}

选择循环
当多个channel需要读取数据的时候,就必须使用 for+select

func f1(c chan int, s chan string) {
	for i := 0; i < 10; i++ {
		time.Sleep(time.Second)
		c <- i
	}
	s <- "stop"
}

func f2(c chan int, s chan string) {
	for i := 20; i >= 0; i-- {
		time.Sleep(time.Second)
		c <- i
	}
	s <- "stop"
}

func main() {
	c1 := make(chan int)
	c2 := make(chan int)
	signal := make(chan string, 10)

	go f1(c1, signal)
	go f2(c2, signal)
LOOP:
	for {
		select {
		case data := <-c1:
			fmt.Println("c1 data is ", data)
		case data := <-c2:
			fmt.Println("c2 data is ", data)
		case data := <-signal:
			fmt.Println("signal is ", data)
			break LOOP
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值