并发导致的问题
var money int = 0
func add(pint *int) {
for i := 0; i < 100000; i++ {
*pint++
}
}
func AddMoney() {
for i := 0; i < 1000; i++ {
// 这里使用了协程调用,导致多个协程调用同一个资源,产生资源竞争,结果不正确
go add(&money)
}
time.Sleep(time.Second*5)
fmt.Println(money)
}
预期结果是100000000,但是由于add方法是协程方式调用的,存在并发的情况,导致结果不符合预期。
读写锁小例子
// 全局变量
var money int = 0
// 初始化锁
var lock *sync.RWMutex = new(sync.RWMutex)
func add2(pint *int) {
// 为了避免资源竞争,操作时需要上锁
// 加锁
lock.Lock()
for i := 0; i < 10000; i++ {
*pint++
}
// 解锁
lock.Unlock()
}
func main() {
for i := 0; i < 1000; i++ {
// 这里使用了协程调用,导致多个协程调用同一个资源,产生资源竞争,结果不正确
go add2(&money)
}
time.Sleep(time.Second*5)
fmt.Println(money)
}
这个例子里,写操作时加了写锁,也就是说整个写得过程是串行的,也就解决了上面的问题。
线程安全的队列
// 线程安全的队列
type Queue struct {
// 数据
data []interface{}
// 长度
len int
// 锁
lock *sync.Mutex
}
// 初始化线程安全队列
func NewQueue() *Queue {
// 初始化结构体
myQueue := new(Queue)
// 开辟内存空间
myQueue.data = make([]interface{}, 0, 10)
// 初始化当前长度
myQueue.len = 0
myQueue.lock = new(sync.Mutex)
return myQueue
}
// 获取队列长度,解决了线程安全
func (queue *Queue) Len() int {
queue.lock.Lock()
defer queue.lock.Unlock()
return queue.len
}
// 判断是否空,解决了线程安全
func (queue *Queue) IsEmpty() bool {
queue.lock.Lock()
defer queue.lock.Unlock()
return queue.len == 0
}
// 弹出第一个元素,解决了线程安全
func (queue *Queue) Shift() (el interface{}) {
queue.lock.Lock()
defer queue.lock.Unlock()
// 为空
if queue.Len() == 0 {
return nil
}
// 第一个元素、数据
el, queue.data = queue.data[0], queue.data[1:]
return
}
// 压入元素,解决了线程安全
func (queue *Queue) Push(el interface{}) {
queue.lock.Lock()
defer queue.lock.Unlock()
queue.data = append(queue.data, el)
queue.len++
return
}
// 获取第一个元素,解决了线程安全
func (queue *Queue) Front() interface{} {
queue.lock.Lock()
defer queue.lock.Unlock()
// 为空
if queue.Len() == 0 {
return nil
}
return queue.data[0]
}
// 获取最后一个元素,解决了线程安全
func (queue *Queue) End() interface{} {
queue.lock.Lock()
defer queue.lock.Unlock()
// 为空
if queue.Len() == 0 {
return nil
}
return queue.data[queue.Len()-1]
}
仿照了上面的思想,在每个读写操作的时都加上读写锁,这样子就可以实现队列的线程安全。