管道和协程

协程

协程基本

协程是以函数的形式进行的

语法

go 函数名()

依赖包

sync中的WaitGroup

var wg sync.WaitGroup

wg.Add()添加一个协程

wg.Done()减少一个协程

wg.Wait()主线程使用,等待协程完成够做后再往下走。

recover异常捕获

在开发中会开启多个协程,一个携程出错会导致整个程序panic而停止,

使用recover捕获异常,可以使一个协程出错panic,而不会导致其他携程也停止运行。

eg:

package main

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

var wg sync.WaitGroup

func sayhello() {
    for i := 0; i < 10; i++ {
        fmt.Println("hello")
        time.Sleep(time.Second)
    }
    wg.Done()
}

func test() {

    //这里的defer+recover机制不会影响其它协程执行
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("test():", err)
        }
    }()
    var mapint map[int]string
    mapint[1] = "goalng" //err没有make,该test携程会出错
    wg.Done()
}
func main() {

    wg.Add(2)
    go sayhello()
    go test()
    wg.Wait()
}

管道

管道基本

1.管道就是队列

2.多个goroutine操作管道是安全的

3.管道有类型,一个string类型的管道只能存放string类型

4.管道是引用类型,要make分配内存空间后才可以使用

5.管道不同于map,不会自动增长,超过上限会报错,管道内没有数据是再取出也会报错。

6.尾进头出,先进先出

语法

1.初始化

var mychan chan int

var 名字 chan 类型

2.make分配内存

mychan=make(chan int ,10)

mychan=make(chan 类型,容量)

2.存入:

mychan

存入时只能存入相同的类型

注意事项

想存入不同类型,要将管道定义成空接口类型,(任何类型都实现了空接口),但要是结构体的话,要进行类型断言

实例

存入:

catchan:=make(chan interface{},3)

cat:=Cat{

Name:”tom“

Age:=10

}

catchan

取出

newcat:=

fmt.Println(newcat.Name) //这个是错的默认是空接口类型,而接口类型没有字段

//进行类型断言

cat1:=newcat.(Cat)

fmt.Println(cat1.Name) //成功

channel关闭

关闭

使用内置函数close可以关闭channel 关闭之后不能在存入数据了,但是可以读取数据

遍历

遍历时要关闭channel,否则会出现错误

不能用for-i遍历

要用for-range遍历

管道阻塞机制

当管道容量小余要存入的数据,并且只进不读,就会出现管道阻塞deadlock

写入管道和读取管道的频率不一致无所谓

channel细节

1.channel可以声明为只读或只写

eg:

ch:=make(chan ch1:=make(

2.select使用

当不确定什么时候关闭channel时,直接读取的话会造成死锁(deadlock)

使用select则不会会造成阻塞

如果当前select的case中没有满足,则会自动跳到下一个

eg:

func main() {
    //使用select可以解决从管道取数据的阻塞问题
    intchan := make(chan int, 10)
    for i := 0; i < 10; i++ {
        intchan <- i
    }
    //定义一个管道,5个string
    stringchan := make(chan string, 5)
    for i := 0; i < 5; i++ {
        stringchan <- "hello" + fmt.Sprintf("%d", i)
    }

    //传统的方法在遍历管道是会阻塞而导致阻塞,而导致死锁

    //问题在实际开发中,可能我们不确定什么时候关闭管道
    //使用select可以解决问题
lable:
    for {
        select {
        case v := <-intchan:
            //如果intchan一直没有关闭,不会一直阻塞而导致死锁
            //会自动到下一个case进行匹配
            fmt.Printf("从intchan 读取了数据%v\n", v)
        case v := <-stringchan:
            fmt.Printf("从stringchan中取出的数据%v\n", v)
        default:
            fmt.Println("都没啦")
            break lable
        }
    }
}

go 的协程并不是线程安全的,对于一些场景,我们希望可以进行资源的控制,否则很容易造成数据的错误。

比如说 加N操作, A 和 B 都看到当前的数字为 10, A想加100,B想加 200 ,于是他们同时发出了修改请求,对于A 、B来说,正确的结果应该分别为 110和210, 但是最终的结果却是 310 。 为了保证正确的结果,在对其进行操作时,应该限制同时只能由单个人去处理。

资源的共享,必然会引起竞争,为了避免竞争所带来的的数据错误影响,我们应该保证共享的资源同一时刻只有一个人可以操作,这就是并发编程的弊端,通过锁的方式保证的数据一致性,却牺牲了部分性能。

在 go 语言中,锁主要分为两种:互斥锁和读写锁

3.1 互斥锁 (mutex)

我们先举一个没有锁机制的并发代码

package main import ( "fmt" "time" ) func main() { count := 0 for r := 0; r < 1000; r++ { go func() { count += 1 }() } time.Sleep(time.Second * 3) fmt.Println("the count is : ", count) }

此时count 的值一般是低于 1000 的,因为部分协程拿到的值是同一个,并且 count+=1 操作并不是原子操作。

//互斥锁是一个互斥锁。 //互斥锁的零值是未锁定的互斥锁。 //互斥锁不能在第一次使用后被复制。 type Mutex struct { state int32 sema uint32 } //Locker接口只有两个方法,一个加锁,一个解锁。 type Locker interface { Lock() Unlock() } //锁定当前的互斥量 //如果锁已被使用,则调用的 goroutine 将阻塞直到互斥锁可用。 func (m *Mutex) Lock() //对当前互斥量进行解锁 //如果在进入解锁时未锁定m,则为运行时错误。 //锁定的互斥锁与特定的 goroutine 无关。 //允许一个goroutine锁定Mutex然后安排另一个goroutine来解锁它。 func (m *Mutex) Unlock()

就可以解决并发导致的问题。

我们可以发现这种方式,是对某个资源的上锁,一旦这个资源被上锁,该资源则无法被抢占。

但是大多数情况,我们是希望可并发读不可并发写的。

3.2 读写锁 (RWMutex)

读写锁是针对读写操作的互斥锁,可以分别针对读操作与写操作进行锁定和解锁操作 。

其访问控制规则如下:

多个写操作之间是互斥的

写操作与读操作之间也是互斥的

多个读操作之间不是互斥的

在这样的控制规则下,读写锁可以大大降低性能损耗。

// RWMutex是一个读/写互斥锁,可以由任意数量的读操作或单个写操作持有。 // RWMutex的零值是未锁定的互斥锁。 //首次使用后,不得复制RWMutex。 //如果goroutine持有RWMutex进行读取而另一个goroutine可能会调用Lock,那么在释放初始读锁之前,goroutine不应该期望能够获取读锁定。 //特别是,这种禁止递归读锁定。 这是为了确保锁最终变得可用; 阻止的锁定会阻止新读操作获取锁定。 type RWMutex struct { w Mutex //如果有待处理的写操作就持有 writerSem uint32 // 写操作等待读操作完成的信号量 readerSem uint32 //读操作等待写操作完成的信号量 readerCount int32 // 待处理的读操作数量 readerWait int32 // number of departing readers } //读操作锁定 func (rw *RWMutex) RLock() //读操作解锁 func (rw *RWMutex) RUnlock() //写操作锁定 func (rw *RWMutex) Lock() //写操作解锁 func (rw *RWMutex) Unlock() //返回一个实现了sync.Locker接口类型的值 func (rw *RWMutex) RLocker() Locker

因为读写锁控制的多个读操作并不是互斥的,所以对于同一个读写锁,添加了多少个读锁定,必须要有等量的读解锁,否则其他协程将没有机会进行资源的操作。

Unlock方法会试图唤醒所有因为进行读锁定而被阻塞的协程

RUnlock 只会在已无任何读锁定的情况下,试图唤醒一个因欲进行写锁定而被阻塞的协程。

若对一个未被写锁定的读写锁进行写解锁,就会引发一个不可恢复的panic,同理对一个未被读锁定的读写锁进行读解锁也会如此。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值