12 go 并发编程

1. 概述

//python java php 多线程编程,多用户编程,多线程和多进程存在的问题主要是耗费内存。

//内存 线程切换 web2.0 用户级线程 绿城 轻量级线程 协程 

// 内存占用小(2k)、切换快,go 语言的协程, go语言诞生之后只有协程可用 goroutine  非常方便

package main

import (
"fmt"
"time"
)

func asyncPrint() {
fmt.Println("bobby")

}
func main() {
go asyncPrint()
fmt.Println("main")
time.Sleep(time.Second)

}

2.匿名函数启动go routine

func main() {
    // 匿名函数启动go routine
    go func() {
        for {
            fmt.Println("bobby")
            time.Sleep(time.Second)
        }

    }()
    fmt.Println("main")
    time.Sleep(10 * time.Second)
}

3. for 循环和闭包

func main() {
    // 匿名函数启动go routine
    // 1、闭包 2、 for 循环的问题。 for 循环的时候每次这个变量会被重用。
    // 由于每次for循环的时候,i变量会被重用,当我进行到第二轮的时候这个i就变了
  // 主要原因是goroutine 是并行的执行的和for循环执行是异步的取的数字 可能重复
for i := 0; i < 100; i++ { fmt.Println("这里是", i) go func() { fmt.Println(i) }() } fmt.Println("main") time.Sleep(10 * time.Second) }
这里是 0
这里是 1..........
这里是 66
66
....
58
这里是 68.....
67
.....
7
这里是 86
这里是 87
这里是 88

goroutine中的i变量是闭包中的一个变量,它引用的是循环变量i的当前值。由于goroutine是并发执行的,它们可能不会按照创建它们的顺序执行。因此,当goroutine执行时,循环可能已经完成了多次迭代,i的值可能已经不是goroutine启动时的值了。


在这段代码中,所有的goroutine都会打印同一个值,即循环结束时i的值,也就是100。这是因为在goroutine被调度执行时,它们捕获的是循环变量i的最终值。


为了在goroutine中正确打印每次迭代的i值,你需要确保每个goroutine捕获的是迭代时的i值的一个副本。这可以通过传递i给匿名函数来实现:


go
func main() {
	for i := 0; i < 100; i++ {
		go func(i int) {
			fmt.Println(i)
		}(i) // 将当前的i值作为参数传递给匿名函数
	}

	fmt.Println("main")
	time.Sleep(10 * time.Second) // 等待所有goroutine执行完成
}

在这个修改后的代码中,每次迭代都会创建一个新的i值的副本,并将其传递给匿名函数。这样,每个goroutine都会打印出它被创建时的i值。由于goroutine的调度和执行可能仍然不是顺序的,所以打印的顺序可能仍然是随机的,但至少每个goroutine打印的值将是正确的。

 

4.sync.WaitGroup 的使用

package main

import (
"fmt"
"sync"
)

// 子的goroutine如何知道主的goroutine已经结束了,主的goroutine如何知道子的goroutine已经结束了
func main() {
var wg sync.WaitGroup
//我要监控多少个goroutine执行结束
wg.Add(100)
for i := 0; i < 100; i++ {
go func(i int) {
defer wg.Done()
fmt.Println(i)
//wg.Done和Add是同时出现的

}(i)
}
//waitGroup主要用于goroutine的执行等待,Add方法要和Done方法配套
wg.Wait()
//time.Sleep(time.Second)

}

 锁的竞争,由于锁的竞争导致值有多种情况

package main

import (
    "fmt"
    "sync"
)

/*
*
锁的竞争
*/
var total int
var wg sync.WaitGroup

func add() {
    defer wg.Done()
    for i := 0; i < 1000000; i++ {
        total += 1

    }
}
func del() {
    defer wg.Done()
    for j := 0; j < 1000000; j++ {
        total -= 1
    }
}

func main() {
    wg.Add(1)
    go add()
    go del()
    wg.Wait()
    fmt.Println(total)

}

 

 

package main

import (
    "fmt"
    "sync"
)

/*
*
锁的竞争
*/
var total int
var wg sync.WaitGroup
var lock sync.Mutex

func add() {
    defer wg.Done()
    for i := 0; i < 1000000; i++ {
        lock.Lock()
        total += 1
        lock.Unlock()

    }
}
func del() {
    defer wg.Done()
    for j := 0; j < 1000000; j++ {
        lock.Lock()
        total -= 1
        lock.Unlock()
    }
}

func main() {
    wg.Add(2)
    go add()
    go del()
    wg.Wait()
    fmt.Println(total)

}

 写锁可以锁定读锁不再执行

package main

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

/*
*
锁的竞争
*/
// 锁本质上是将并行的代码串行化了,使用lock肯定影响性能。
// 即使是设计锁,那么也应该尽量的保证执行。
// 我们有两组协程,其中一组负责写数据,另一住负责读数据,web系统中大部分的场景都是读多写少。
// 虽然有多个goroutine,但是仔细分析发现,读协程之间应该并发,读和写之间应该串行。读和读之间不应该并行
// 读写锁

func main() {

    var rwlock sync.RWMutex
    var wg sync.WaitGroup

    wg.Add(6)
    go func() {
        time.Sleep(time.Second)
        defer wg.Done()
        rwlock.Lock() // 加写锁  写锁会防止别的读锁 或者写锁获取
        fmt.Println("write")
        time.Sleep(time.Second * 5)
        rwlock.Unlock()

    }()

    for i := 0; i < 5; i++ {
        go func() {
            defer wg.Done()
            for {
                rwlock.RLock() // 加读锁 读锁不会阻止别人读取
                time.Sleep(500 * time.Millisecond)
                fmt.Println("read")
                rwlock.RUnlock()
            }
        }()

    }

    wg.Wait()

}

 

func main() {
    var msg chan string
    msg = make(chan string, 1) // channel的初始化值 如果值为零的话 你放值进去会阻塞
    msg <- "bobby"
    data := <-msg
    fmt.Println(data)
}

 

package main

import (
    "fmt"
    "time"
)

func main() {
    var msg chan int
    //msg = make(chan string, 1) // channel的初始化值 如果值为零的话 你放值进去会阻塞
    msg = make(chan int, 2) // 无缓冲的channel 如果职位0 放进去值会阻塞
    go func(msg chan int) { // go 有一种happen-before 机制可以保障
        data := <-msg

        fmt.Println(data)
        data = <-msg
        fmt.Println(data)
    }(msg)
    msg <- 1
    msg <- 2
    // 容易出现dead-lock的情况
    // waitgroup 少了done调用
    // 无缓冲的channel 也容易出现dead-lock

    time.Sleep(time.Second)
}

 

package main

import (
    "fmt"
    "time"
)

func main() {
    var msg chan int
    //msg = make(chan string, 1) // channel的初始化值 如果值为零的话 你放值进去会阻塞
    msg = make(chan int, 2) // 无缓冲的channel 如果职位0 放进去值会阻塞
    go func(msg chan int) { // go 有一种happen-before 机制可以保障
        for data := range msg {
            fmt.Println(data)
        }
        fmt.Println("all done")
    }(msg)
    msg <- 1
    msg <- 2
    close(msg) // 已经关闭的closed不能再存值了 可以继续取值
    dt2 := <-msg
    fmt.Println(dt2)
    // 容易出现dead-lock的情况
    // waitgroup 少了done调用
    // 无缓冲的channel 也容易出现dead-lock

    time.Sleep(time.Second)
}

 

func main() {
    /*
        默认情况下 channel都是双向的
        但是 我们经常channel作用参数进行传递,希望对方也是单项使用
        比如只取数 不传入参数
    */
    //var ch1 chan string    // 双向channel
    //var ch2 chan<- float64 // 单向channel只能写入float64数据
    //var ch3 <-chan int     // 单向channel 只能读取数据
    var ch chan int = make(chan int, 5)
    var send chan<- int = ch
    var receive <-chan int = ch
    send <- 99
    <-receive
}

5.channel只读和只写示例。

生产者和消费者channel.

package main

import (
    "fmt"
    "time"
)

// 生产者channel 只能写数据
func producer(pdch chan<- int) {
    for i := 0; i < 10; i++ {
        pdch <- i * i
    }
    close(pdch)

}

// 消费者channel 只能读数据
func consumer(cs <-chan int) {
    for c := range cs {
        fmt.Printf("%d \n\r", c)

    }

}

func main() {
    var ch chan int = make(chan int, 10)
    go producer(ch)
    go consumer(ch)

    time.Sleep(time.Second * 10)
    /*
        默认情况下 channel都是双向的
        但是 我们经常channel作用参数进行传递,希望对方也是单项使用
        比如只取数 不传入参数
    */
    //var ch1 chan string    // 双向channel
    //var ch2 chan<- float64 // 单向channel只能写入float64数据
    //var ch3 <-chan int     // 单向channel 只能读取数据
    //var ch chan int = make(chan int, 5)
    //var send chan<- int = ch    // 只能发送
    //var receive <-chan int = ch // 只能接收
    //send <- 99
    ////<-send 会报错
    //// Invalid operation: <-send (receive from the send-only type chan<- int)
    //<-receive
}

6.channel相互等待通知示例。

package main

import (
    "fmt"
    "time"
)

/*
利用 channel实现交叉打印数字和字母
一个goroutine 打印数字 一个goroutine 打印字母
互相通知
例如如下:
12AB34CD56EF78GH910IJ1112KL1314MN15160P1718QR1920ST2122UV2324WX2526YZ2728
*/
var num, letter = make(chan bool), make(chan bool)

func printNum() {
    i := 1
    fmt.Println("----数字----")
    for {
        // 此处等待另一个goroutine 过来通知
        <-num
        fmt.Printf("%d%d", i, i+1)
        i += 2
        letter <- true
    }
}
func printAlph() {
    var str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    j := 0
    //fmt.Println("----字母----")
    for {
        // 此处等待另一个goroutine 过来通知
        <-letter
        if j >= len(str) {
            return
        }
        fmt.Print(str[j : j+2])
        j += 2
        num <- true
    }
}
func main() {
    go printAlph()
    go printNum()
    num <- true
    time.Sleep(time.Second * 10)

}

7.goroutine 执行完成后立马知道执行完毕。

import (
    "fmt"
    "sync"
    "time"
)
/*
select 类似于switch case 语句,但是select的功能和我们操作Linux里面提供的io的select poll epoll
select 主要用于channel
*/
var lock sync.Mutex
var boo bool
func g1() {
    lock.Lock()
    defer lock.Unlock()
    time.Sleep(time.Second)
    boo = true
}
func g2() {
    lock.Lock()
    defer lock.Unlock()
    time.Sleep(2 * time.Second)
    boo = true
}
func main() {
    go g1()
    go g2()
    for {
        if boo {
            fmt.Println("done")
            time.Sleep(10 * time.Millisecond)
            return
        }
    }

}

 

var done = make(chan struct{}) // channel 是多线程安全的,channel要初始化

func g1() {

    time.Sleep(10 * time.Second)
    done <- struct{}{}
}
func g2() {

    time.Sleep(4 * time.Second)
    done <- struct{}{}
}
func main() {
    go g1()
    go g2()
    <-done
    fmt.Println("done")

}

8.select 接收多个goroutine 的执行结果

func g1(ch1 chan struct{}) {

    time.Sleep(3 * time.Second)
    ch1 <- struct{}{}
}
func g2(ch2 chan struct{}) {

    time.Sleep(4 * time.Second)
    ch2 <- struct{}{}
}
func main() {
    g1Channel := make(chan struct{})
    g2Channel := make(chan struct{})
    go g1(g1Channel)
    go g2(g2Channel)
    // 我要监控多个channel任何一个有值都知道
    //
    select {
    case <-g1Channel:
        fmt.Println("g1 done")
    case <-g2Channel:
        fmt.Println("g2 done")
    }

}

 

posted @ 2024-09-23 15:19  滴滴滴  阅读(12)  评论(0编辑  收藏  举报