go 并发编程

进程

线程

协程

设置golang运行cpu数

1.主线程和协程同时执行

package main

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

func test(){
    for i:=1;i<=10;i++{
        fmt.Println("test() hello world"+strconv.Itoa(i))
        time.Sleep(time.Second)
    }
}

//开启协程
func main(){
    go test()//开启一个协程
    for i:=1;i<=10;i++{
        fmt.Println("main() hello,goang"+strconv.Itoa(i))
        time.Sleep(time.Second)
    }

}
//主线程和协程同时执行
View Code

 2.设置golang运行cpu数

package main

import (
    "fmt"
    "runtime"
)

//设置golang运行cpu数
func main(){

    //获取当前系统cpu数量
    num:=runtime.NumCPU()
    //我这里设置num-1的cpu运行go程序
    runtime.GOMAXPROCS(num)
    fmt.Println("num=",num)
}
View Code

3.加锁

package main

import (
    "fmt"
    "sync"//互斥锁
)

//求1-n每个数的阶乘
var (
    myMap = make(map[int]int, 10)
    lock sync.Mutex
)

func test(n int) {
    res := 1
    for i := 1; i <= n; i++ {
        res *= i
    }
    //存入map
    //加锁
    lock.Lock()
    myMap[n] = res
    lock.Unlock()
}

func main() {
    for i := 1; i <= 60; i++ {
        go test(i)
    }
    //输出
    //加锁避免还未生成完就进行输出
    lock.Lock()
    for i, v := range myMap {
        fmt.Printf("map[%d]=%d
", i, v)
    }
    lock.Unlock()
}
View Code

管道

 1.管道说明

package main

import "fmt"

//管道
func main() {
    //1.创建一个存放3个int类型的管道
    var intChan chan int
    intChan = make(chan int, 3)
    //看看intChan是什么
    fmt.Printf("intChan 的值=%v intChan 本身的地址=%p
", intChan, &intChan)

    //3.向管道写入数据
    intChan <- 10
    num := 211
    intChan <- num
    intChan <- 50
    //intChan<-98//当我们写入数据时,不能超过其容量

    //4.看看管道长度和cap(容量)
    fmt.Printf("channel len=%v cap=%v
", len(intChan), cap(intChan))
    //5.从管道中读取数据
    var num2 int
    num2=<-intChan
    fmt.Println("num2=",num2)
    fmt.Printf("channel len=%v cap=%v
",len(intChan),cap(intChan))
    //6.在没有使用协程的情况下,如果我们的管道数据已经全部取出,再取就会报deadlock
    num3:=<-intChan
    num4:=<-intChan
    num5:=<-intChan
//会报错
    fmt.Println("num3=",num3,"num4=",num4,"num5=",num5)
}
View Code

2.管道的读写演示

package main

import (
    "fmt"
)

func c1() {
    var intChan chan int
    intChan = make(chan int, 3)
    intChan <- 10
    intChan <- 20
    intChan <- 10

    num1 := <-intChan
    num2 := <-intChan
    num3 := <-intChan
    fmt.Printf("num1=%v num2=%v num3=%v", num1, num2, num3)
}

//存放map

func c2() {
    var mapChan chan map[string]string
    mapChan = make(chan map[string]string, 10)
    m1 := make(map[string]string, 20)
    m1["city1"] = "北京"
    m1["city2"] = "天津"

    m2 := make(map[string]string, 20)
    m2["hero1"] = "宋江"
    m2["hero2"] = "武松"
    mapChan <- m1
    mapChan <- m2

}

//结构体变量
type Cat struct {
    Name string
    Age  int
}

func chanStruct() {
    var catChan chan Cat
    catChan = make(chan Cat, 10)
    cat1 := Cat{Name: "tom1", Age: 18}
    cat2 := Cat{Name: "tom2", Age: 28}
    catChan <- cat1
    catChan <- cat2
    fmt.Println(cat1, cat2)
}

func chanStruct2() {
    var catChan chan *Cat
    catChan = make(chan *Cat, 10)

    cat1 := Cat{Name: "tom1", Age: 18,}
    cat2 := Cat{Name: "tom2", Age: 28,}
    catChan <- &cat1
    catChan <- &cat2
    //取出
    cat11 := <-catChan
    cat22 := <-catChan
    fmt.Println(cat11, cat22)
}

//存放任意数据类型
func chanOther() {
    var allChan chan interface{}
    allChan = make(chan interface{}, 10)

    cat1 := Cat{Name: "tom1", Age: 19,}
    cat2 := Cat{Name: "tom2", Age: 29,}
    allChan <- cat1
    allChan <- cat2
    allChan <- 10
    allChan <- "jack"

    //取出

    cat11 := <-allChan
    cat22 := <-allChan
    v1 := <-allChan
    v2 := <-allChan
    fmt.Println(cat11, cat22, v1, v2)
}

//取出结构体属性需要使用类型断言
func c6() {
    var allChan chan interface{}
    allChan = make(chan interface{}, 10)

    cat1 := Cat{Name: "tom1", Age: 19,}
    cat2 := Cat{Name: "tom2", Age: 29,}
    allChan <- cat1
    allChan <- cat2
    allChan <- 10
    allChan <- "jack"

    //取出

    cat11 := <-allChan
    //使用类型断言,恢复结构体形式
    a := cat11.(Cat)
    fmt.Println(a.Name)
}

//管道的读写演示
func main() {
    c6()
}
View Code

3.channel遍历以及关闭

package main

import "fmt"

//channel遍历以及关闭
func chanClose(){

    intChan :=make(chan int,3)
    intChan<-100
    intChan<-200
    close(intChan)//关闭
    //关闭后,不能继续写入,但可以读取
}
//channel遍历
//1.遍历时如果没有关闭,则会出现deadlock错误
//2.如果已经关闭,遍历完成后,就会退出
func chanFor(){
    intChan2:=make(chan int,100)
    for i:=0;i<100;i++{
        intChan2<-i*2
    }
    //遍历管道不能使用for
    close(intChan2)
    for v:=range intChan2{
        fmt.Println("v=",v)
    }
}

func main(){

chanFor()
}
View Code

4.最佳案例1使用协程

package main

import (
    "fmt"
    "time"
)

func writeData(intChan chan int) {
    for i := 1; i <= 50; i++ {
        //放入数据
        intChan <- i
        fmt.Println("writeData", i)
    }
    close(intChan)
}

func readData(intChan chan int, exitChan chan bool) {
    for {
        v, ok := <-intChan //接收读取的内容,错误
        //取完之后再取,返回false,
        //fmt.Printf("布尔值 %v
",ok)
        if !ok {
            break
        }

        fmt.Printf("readData 读取到数据=%v
", v)
    }
    //标记任务完成
    exitChan <- true
    close(exitChan)
}

func main() {
    //创建两个管道
    intChan := make(chan int, 50)
    exitChan := make(chan bool, 1)
    //启用协程
    go writeData(intChan)
    go readData(intChan, exitChan)
    time.Sleep(time.Second * 1) //1秒
    //每次到这里检查是否读完了
    for {
        _, ok := <-exitChan
        if !ok {
            fmt.Println("结束了")
            break
        }
    }
}
View Code

5.多协程取素数

package main

import (
    "fmt"
    "time"
)

//统计1-n素数

//向intChan放入1-n个数
func putNum(intChan chan int) {
    for i := 1; i <= 8000; i++ {
        intChan <- i
    }
    close(intChan)
}

//取出数据判断是否为素数,如果是就放书primeChan
func primeNum(intChan chan int, primeChan chan int, exitChan chan bool) {
    //使用for循环
    var flag bool
    for {
        time.Sleep(time.Millisecond * 10)
        num, ok := <-intChan
        if !ok {
            break
        }

        flag = true
        for i := 2; i < num; i++ { //不是素数
            if num%i == 0 {
                flag = false
                break
            }
        }
        if flag {
            //素数存入
            primeChan <- num
        }
    }
    fmt.Println("有一个primeNUm 协程,因为取不到数据,退出")
    //标记读取完成
    exitChan <- true
}
func main() {
    intChan := make(chan int, 1000)
    primeChan := make(chan int, 2000)
    //标识退出的管道
    exitChan := make(chan bool, 4)
    //开启一个协程,向intChan放入1-8000个数
    go putNum(intChan)
    //开启四个协程,从intChan取出数据,并判断是否为素数,如果是就放入
    for i := 0; i < 4; i++ {
        go primeNum(intChan, primeChan, exitChan)
    }
    //这里主线程 处理
    go func() {
        for i := 0; i < 4; i++ {
            <-exitChan
        }
        close(primeChan)
    }()
    //遍历primeChan,把结果取出
    for{
        res,ok:=<-primeChan
        if !ok{
            break
        }

        //输出
        fmt.Printf("素数=%d
",res)
    }
    fmt.Println("线程退出")

}
View Code

6.只读,只写

//1.channel可以生名为只读,或者只写性质
func g1(){

    //1.可读可写
    //var chan1 chan int
    //2.声明为只写
    var chan2 chan<-int
    chan2=make(chan int,3)
    chan2<-20
    fmt.Println("chan2=",chan2)
    //3.声明为只读
    //var chan3  <-chan int
    //num2:=<-chan3
}
View Code

7.测试只读,只写

package main

import (
    "fmt"
)

//1.channel可以生名为只读,或者只写性质
func g1() {

    //1.可读可写
    //var chan1 chan int
    //2.声明为只写
    var chan2 chan<- int
    chan2 = make(chan int, 3)
    chan2 <- 20
    fmt.Println("chan2=", chan2)
    //3.声明为只读
    //var chan3  <-chan int
    //num2:=<-chan3
}

//只写
func send(ch chan<- int, exitchan chan struct{}) {
    for i := 0; i < 10; i++ {
        ch <- i
    }
    close(ch)
    var a struct{}
    exitchan <- a
}

//只读
func recv(ch <-chan int, exitChan chan struct{}) {
    for {
        v, ok := <-ch
        if !ok {
            break
        }
        fmt.Println(v)
    }
    var a struct{}
    exitChan <- a
}

func main() {
    var ch chan int
    ch = make(chan int, 10)
    exitChan := make(chan struct{}, 2)
    go send(ch, exitChan)
    go recv(ch, exitChan)
    var total = 0
    for _ = range exitChan {
        total++
        if total == 2 {
            break
        }
    }
    fmt.Println("结束。。。")

}
View Code

8.使用select可以解决从通道取数据阻塞问题

package main

import (
    "fmt"
    "time"
)

//使用select可以解决从通道取数据阻塞问题

func main(){
    //定义一个管道10个数据int
    intChan:=make(chan int,10)
    for i:=0;i<10;i++{
        intChan<- i
    }
    //2.定义一个管道5个数据string
    stringChan:=make(chan string,10)

    for i:=0;i<5;i++{
        stringChan<-"hello"+fmt.Sprintf("%d",i)
    }
    //传统方法在遍历管道时,如果不关闭会阻塞导致deadlock
    //问题,在实际开发中我们不好确定什么关闭管道
    //可以使用select方式可以解决
    //label:
    for{
        select{
        //注意:这里,如果intChan一直没有关闭,不会一直阻塞而deadlock
        //会自动到下一个case匹配
        case v :=<-intChan:
            fmt.Printf("从intChan读取的数据%d
",v)
        time.Sleep(time.Second)
            case v:=<-stringChan:
                fmt.Printf("从stringChan读取的数据%v
",v)
                time.Sleep(time.Second)
        default:
            fmt.Printf("都取不到了,程序可以加入逻辑
")
            time.Sleep(time.Second)
            return
            //break label
        }
    }
}
View Code

9.goroutine中使用recover,解决协程中出现panic ,导致程序崩溃

package main

import (
    "fmt"
    "time"
)

//goroutine中使用recover,解决协程中出现panic ,导致程序崩溃

//
func sayHello() {
    for i := 0; i < 10; i++ {
        time.Sleep(time.Second)
        fmt.Println("hello world")
    }
}

func test() {
    defer func() {
        //捕捉test抛出的panic
        if err := recover(); err != nil {
            fmt.Println("test() 发生错误", err)
        }
    }()
    //定义一个map
    var myMap map[int]string
    myMap[0] = "golang"

}

func main() {
    go sayHello()
    go test()
    for i := 0; i < 10; i++ {
        fmt.Println("main() ok=", i)
        time.Sleep(time.Second)
    }
}
View Code

借助scanner阻塞主线程结束

func main(){
go func() {
    var times int
    for{
        times++
        fmt.Println("tick",times)
        time.Sleep(time.Second)
    }
}()
//不输入实际对主协程进行了阻塞
var input string
fmt.Scanln(&input)
}
View Code

 多个子协程调用顺序是随机的

func printNum() {
    for i := 1; i < 5; i++ {
        time.Sleep(250 * time.Millisecond)
        fmt.Printf("%d", i)
    }
}

func printLetter() {
    for i := 'a'; i < 'e'; i++ {
        time.Sleep(400 * time.Millisecond)
        fmt.Printf("%c", i)
    }
}
func main() {
    go printNum()
    go printLetter()
    time.Sleep(3 * time.Second)
    fmt.Println("
 main over")

}
View Code

 生产消费模型

//借助channel实现生产消费模型
func main() {
    ch1 := make(chan int)
    //生产
    go producer(ch1)
    //消费
    ch_bool1 := make(chan bool)
    ch_bool2 := make(chan bool)
    ch_bool3 := make(chan bool)
    go customer("jack", ch1, ch_bool1)
    go customer("rose", ch1, ch_bool2)
    go customer("anner", ch1, ch_bool3)
    <-ch_bool1
    <-ch_bool2
    <-ch_bool3
    fmt.Println("main over")
}

func producer(ch chan int) {
    for i := 1; i <= 10; i++ {
        ch <- i
        fmt.Printf("生产%d号面包
", i)
        time.Sleep(time.Second)
    }
    close(ch)
}

//消费面包
func customer(name string, ch chan int, ch_bool chan bool) {
    for data := range ch {
        fmt.Printf("%v吃了%d号面包
", name, data)
        //    time.Sleep(time.Second)
    }
    ch_bool <- true
    close(ch_bool)
}
View Code

 timer

延迟存入通道

func main(){
    //创建计时器
    //4秒后存入通道
    timer1:=time.NewTimer(4*time.Second)
    fmt.Println(time.Now())
    data:=<-timer1.C//取到之前会被阻塞
    fmt.Printf("timer_t=%T
",timer1.C)
    fmt.Printf("data=%T
",data)
    fmt.Println("da",data)
}
View Code

after延迟存入

//after使用
//延迟存入通道
func main() {
    //2.使用After(),返回值<-chan Time,同Timer.C
    ch1 := time.After(5 * time.Second)
    fmt.Println(time.Now())
    data := <-ch1
    fmt.Printf("data_type=%T
", data)
    fmt.Println("data", data)
}
View Code

sync

防止主线程先于子goroutine结束

func main() {
    var wg sync.WaitGroup
    fmt.Printf("%T
", wg)
    wg.Add(3)
    rand.Seed(time.Now().UnixNano())
    go printNum(&wg, 1)
    go printNum(&wg, 2)
    go printNum(&wg, 3)
    wg.Wait() //进入阻塞状态
    defer fmt.Println("main over")
}
func printNum(wg *sync.WaitGroup, num int) {
    for i := 1; i <= 3; i++ {
        pre := strings.Repeat("	", num-1)
        fmt.Printf("%s第%d号子goroutine,%d
", pre, num, i)
        time.Sleep(time.Second)
    }
    wg.Done()
}
View Code

 互斥锁实现购票

//互斥锁
var tickets int = 20
var wg sync.WaitGroup
var mutex sync.Mutex

func main() {
    wg.Add(4)
    go saleTickets("1号窗口", &wg)
    go saleTickets("2号窗口", &wg)
    go saleTickets("3号窗口", &wg)
    go saleTickets("4号窗口", &wg)
    wg.Wait()
    defer fmt.Println("所有車票售空")
}

func saleTickets(name string, wg *sync.WaitGroup) {
    defer wg.Done()
    for {
        //锁定
        mutex.Lock()
        if tickets > 0 {
            time.Sleep(1 * time.Second)
            //获取窗口的编号
            num, _ := strconv.Atoi(name[:1])
            pre := strings.Repeat("--", num)
            fmt.Println(pre, name, tickets)
            tickets--
        } else {
            fmt.Printf("%s 结束售票
", name)
            mutex.Unlock()
            break
        }
        //解锁
        mutex.Unlock()
    }
}
View Code

读写互斥锁

//读写互斥锁
func main() {
    var rwm sync.RWMutex
    for i := 1; i <= 3; i++ {
        go func(i int) {
            fmt.Printf("goroutine %d,尝试读锁定。
", i)
            rwm.RLock()
            fmt.Printf("goroutine %d 已经锁定了
", i)
            time.Sleep(5 * time.Second)
            fmt.Printf("goroutine %d,读解锁", i)
            rwm.RUnlock()
        }(i)
    }

    time.Sleep(1 * time.Second)
    fmt.Println("main.,尝试写锁定")
    rwm.Lock()
    fmt.Println("main 已经锁定了")
    rwm.Unlock()
    fmt.Println("main 写解锁")
}
View Code

解决资源竞争

原文地址:https://www.cnblogs.com/huay/p/12171695.html