golang之线程安全


阅读本文前需要对并发机制有了解
只要是多线程,操作全局变量,就会有线程安全的问题,跟语言无关,同样,go也有互斥锁,读写锁这些。注意:go里面精确的名字是goroutine,这里我就叫做线程(什么名字无所谓,理解即可)

看一个线程不安全的例子:

package main

import (
	"fmt"
	"sync"
	"time"
)

var n = 0
func main() {
	for j := 0;j<10;j++ {
		go func() {
			for i := 0;i<10000;i++ {
				n++
			}
		}()
	}
	for {
		time.Sleep(time.Millisecond * 200)
		fmt.Println(n)
	}
}

结果肯定不是100000
要想解决这个问题:就是加锁

互斥锁

写go代码的人是幸福的,代码就是这么简洁。

package main

import (
	"fmt"
	"sync"
	"time"
)

var mux sync.Mutex
var n = 0
func main() {
	for j := 0;j<10;j++ {
		go func() {
			for i := 0;i<10000;i++ {
				mux.Lock()
				n++
				mux.Unlock()
			}
		}()
	}
	for {
		time.Sleep(time.Millisecond * 200)
		fmt.Println(n)
	}
}

读写锁

如果读的场景远大于写的场景,用读写锁性能更好,更易发挥多核CPU优势,如下20个goroutine读,2个写,用读写锁更好

package main

import (
	"fmt"
	"sync"
	"time"
)

var mux sync.RWMutex
var n = 0
func main() {
	for j := 0;j<2;j++ {
		go func() {
			for i := 0;i<10000;i++ {
				mux.Lock()
				n++
				mux.Unlock()
			}
		}()
	}
	for j := 0;j<20;j++ {
		go func() {
			for i := 0;i<10000;i++ {
				time.Sleep(time.Millisecond * 200)
				mux.RLock()
				fmt.Println(n)
				mux.RUnlock()
			}
		}()
	}
	for {
		time.Sleep(time.Hour)
	}
}

WaitGroup监控goroutine退出状态

WaitGroup解决的是并发线程的退出问题,通俗点讲,也就是主线程要等所有线程都退出后才能退出。go的实现同样非常简单

package main

import (
	"fmt"
	"sync"
)
// 开一个group,go程有多少就Add多少,当go程退出的时候,就Done把数量减1,用wait监控数量,空了就退出
var wg sync.WaitGroup
func main() {
	for i:=0;i<1000;i++ {
		go func(i int) {
			wg.Add(1)
			defer wg.Done()
			fmt.Println(i)
		}(i)
	}
	wg.Wait()
}

sync.Cond条件变量实现生产者,消费者

这里注意,生产者发出信号后,会有至少一个消费者q.cond.Wait() 获得信号,所以对边界条件需要再次判断
借助sync.Cond可以实现chan
请看我的另一篇文章

package main

import (
	"fmt"
	"strconv"
	"sync"
	"time"
)

type Queue struct {
	queue []string
	cond  *sync.Cond
}

// producer
func (q *Queue) Enqueue(str string) {
	q.cond.L.Lock()
	q.queue = append(q.queue, str)

	q.cond.L.Unlock()
	q.cond.Signal()
}

// consumer
func (q *Queue) Dequeue() string {
	q.cond.L.Lock()
	if len(q.queue) == 0 {
		q.cond.Wait()
	}
	// 为防止多个协程接收到条件成立信号,必须判断
	if len(q.queue) == 0 {
		q.cond.L.Unlock()
		return ""
	}
	str := q.queue[0]
	q.queue = q.queue[1:]
	q.cond.L.Unlock()
	return str
}
func main() {
	q := Queue{
		cond: sync.NewCond(&sync.Mutex{}),
	}
	go func() {
		for i := 0; i < 10000; i++ {
			time.Sleep(time.Millisecond * 100)
			q.Enqueue(strconv.Itoa(i))
		}
	}()
	for {
		time.Sleep(time.Millisecond * 100)
		go func() {
			fmt.Println(q.Dequeue())
		}()
		go func() {
			fmt.Println(q.Dequeue())
		}()
	}
	time.Sleep(time.Hour)
}

死锁(deadlock)产生的原因

死锁的意思就是有一个锁,永远都解不开,阻塞在拿锁的位置,需要注意,死锁不一定报错,只有在主go程中出现死锁才报错,程序挂掉。
死锁产生的原因就2个:

  1. 加锁了,但是没释放,例子:
package main

import (
	"fmt"
	"sync"
	"time"
)

var n int
var mux sync.Mutex

func main() {
	for i := 0; i < 2; i++ {
		go func() {
			mux.Lock()
			n++
			if i > 10 {
				mux.Unlock()
			}
			fmt.Println(n)
		}()
	}

	time.Sleep(time.Hour)
}
  1. 嵌套锁:
package main

import (
	"fmt"
	"sync"
	"time"
)

var n int
var mux sync.Mutex

func main() {
	go func() {
		mux.Lock()
		mux.Lock()
		n++
		mux.Unlock()
		mux.Unlock()
	}()

	time.Sleep(time.Hour)
}

sync.Once保证某段代码只执行一次

单例模式的实现

package main

import (
	"fmt"
	"sync"
	"time"
)

var once sync.Once
var cg *Config

type Config struct {
	Port int
	cg *Config
}

func NewConfig() *Config {
	once.Do(func() {
		cg = &Config{Port: 8888}
	})
	return cg
}

func main() {
	c1 := NewConfig()
	c2 := NewConfig()
	c1.Port = 1000
	fmt.Println(c2.Port)
	time.Sleep(time.Hour)
}
  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值