go中的并发

Golang中的Go Scheduler:深入理解并优化并发调度器 goroutine和channel是Go语言并发编程的核心特性,但过多的goroutine和channel的创建和使用可能会导致调度器的负担增加,进而影响程序性能。在Go语言中,抢占(preemption)是指调度器在一个goroutine执行时间过长时,主动中断它的执行,以便切换到其他等待执行的goroutine。在Go语言中,抢占(preemption)是指调度器在一个goroutine执行时间过长时,主动中断它的执行,以便切换到其他等待执行的goroutine。函数将使用的最大CPU核心数设置为2。 阅读详情

goruntine(协程)

每一个并发的执行单元叫做一个goruntine,要编写一个并发任务,可以在函数名前加go关键字,就能使这个函数以协程的方式运行,

如:go 函数名(函数参数)、

如果函数有返回值,返回值会被忽略。所以当你用go关键字后,主进程调用函数的时候函数的返回值就没有和主进程进行数据交换,而只能用channel

线程和协程的区别:

区别1:线程有固定的栈,基本都是2MB,都是固定分配的;这个栈用于保存局部变量,在函数切换时使用。对于协程来说,固定的栈可能会导致资源浪费,go采用了动态收缩扩张收缩的策略,初始化为2KB,最大可扩张到1GB

区别2:线程切换要陷入内核,进行上下文切换,而协程在用户态由协程调度器完成,不用陷入内核

demo:协程的基本使用

package main

import (
   "fmt"
   "time"
)


func Task1() {
   for {
      fmt.Println(time.Now().Format("10:48:00"), "正在处理task1的任务")
      time.Sleep(time.Second * 3)
   }
}

func Task2() {
   for {
      fmt.Println(time.Now().Format("10:48:00"), "正在处理task2的任务")
      time.Sleep(time.Second * 1)
   }

}

func main() {
   go Task1()
   go Task2()

   for {
      fmt.Println(time.Now().Format("10:48:00"), "正在处理主进程的任务")
      time.Sleep(time.Second * 2)
   }
}

demo2:

package main

import (
   "fmt"
   "time"
)

/*
当一个程序启动时,其主函数即在一个单独的goroutine中运行,我们称之为main goroutine。

下面这个程序运行后不会有任何输出,因为运行go task1(),程序立刻返回到main函数,main函数后没有任何代码逻辑,程序判断为执行完毕,终止所有协程。
如果要让task1函数执行,可以在main函数加上一些等待逻辑,确定子协程执行完毕后结束main函数。如sleep()
*/

func task1() {
   for {
      fmt.Println(time.Now().Format("10:48:00"), "正在处理task1的任务")
      time.Sleep(time.Second * 3)
   }
}

func main() {
   go task1()
   time.Sleep(time.Second * 100)
}

demo3: 使用匿名函数创建goruntine

package main

import (
    "fmt"
    "time"
)

/*
使用匿名函数创建goruntine,格式如下:
go func(参数列表){
    函数体
}(调用参数列表)


*/

func main() {
    go func() {
        for {
            fmt.Println(time.Now().Format("11:17:00"), "正在处理task1的任务!")
            time.Sleep(time.Second * 3)
        }
    }()
    time.Sleep(time.Second * 100)
}

channel

go中推荐使用channel作为goruntine之间同步和通信的手段

常见写法:var channelName chan T

含义:用chan关键字声明了一个channel变量,并指定了传输的数据类型T

channel的发送和接收:channel作为一个队列,收发数据时按先入先出的原则,同一时刻内只能允许一个goruntine访问channel来发送和接受数据

发送:channel<-val

含义:表示将var发送到channel中,如果channel中数据被填满之后会阻塞当前goruntine

接受:val<-channel

含义:表示从channel中读取数据赋值给var上,如果channel中没有数据,会阻塞读取的goruntine直到有数据被放到channel中。

注:可以在读取channel时立刻返回,如 var,ok:=<-channel ,此时检查ok是否为true,用来判断是否读到了有效数据。

初始化:ch:=make(chan T,sizeofChan)

注:

  • 在创建channel时要指定channel传输的数据类型,长度是可选的。
  • 如果不指定数据类型,会导致向channel发送数据的goruntine被阻塞,直到数据被读取
  • 如果指定了长度,表示是有缓冲的channel,在缓冲区未满时发送给数据不会被阻塞。
  • 无论channel是否携带缓冲区,读取的goroutine都会被阻塞,直到channel中有数据可被读取。

demo:下面通过一个例子演示goruntine和channel配合,创建一个channel用于在两个goruntine中收发数据,其中一个goruntine从命令行读取输入发送到channel中,另一个goruntine循环的从channel中读取数据并输出

import (
    "bufio"
    "fmt"
    "os"
)

//从channel中读数据进行打印
func printInput(ch chan string) {
    for val1 := range ch {
        //读取到结束符合
        if val1 == "EOF" {
            break
        }
        fmt.Printf("input is: %s\n", val1)
    }
}

func main() {
    //创建一个无缓冲的channel
    ch := make(chan string)
    go printInput(ch)

    //从命令行读取输入
    scanner := bufio.NewScanner(os.Stdin)

    for scanner.Scan() {
        var1 := scanner.Text()
        //将读到的命令行发送到channel
        ch <- var1
        if var1 == "EOF" {
            fmt.Println("End the game")
            break
        }
    }

    //关闭channel
    defer close(ch)
}

运行结果:

i am cc
input is: i am cc
eof
input is: eof
EOF
End the game

上述例子的代码中,我们通过for:range语法从channel中循环读取数据:当channel中没有数据时,printInput的goroutine将会被阻塞。

代码的最后,我们还通过defer close(ch)关闭了创建的channel,需要注意的是在channel关闭后不允许再往通道中放入数据,不然会抛出panic;

而再从关闭的channel读取数据或者之前从channel读取数据并被阻塞的goroutine将会接收到零值,直接返回。

demo2:带缓冲区的channel

package main

import (
    "fmt"
    "time"
)

/*
带缓冲区的channel:创建channel时指定了channel的长度,那么channel将会拥有缓冲区,goroutine在缓冲区未满时往channel发送数据将不会被阻塞。

*/

func consume(ch chan int) {
    //线程休息30s再从channel中读数据
    time.Sleep(time.Second * 30)
    <-ch
}

func main() {
    //创建一个长度为2的channel
    ch := make(chan int, 2)
    go consume(ch)

    ch <- 0
    ch <- 1

    //发送数据不被阻塞
    fmt.Println("I am free!")
    ch <- 2
    fmt.Println("I can not go there within 100s!")
    time.Sleep(time.Second*2)
}

运行结果:30s之内只输出I am free! ,这段时间内是阻塞的,30s之后协程从channel读数据后,再往里面发送数据2是不阻塞的,所以输出了I can not go there within 30s!

I am free!
I can not go there within 30s!

除了声明双向channel,.我们还可以声明单向的channel,即只能从channel发送数据或者只能从channel中读取数据,代码如下所示:

ch := make(chan T)
// 声明只能发送的通道
var chInput chan <- int = ch

// 声明只能读取的通道
var chOutput int <- chan = ch

demo3:select使用

运行结果:

get value 0 from ch1
get value 1 from ch1 
get value 10 from ch2
get value 11 from ch2
get value 12 from ch2
get value 2 from ch1 
get value 3 from ch1 
get value 13 from ch2
get value 4 from ch1 
get value 5 from ch1 
get value 6 from ch1
get value 14 from ch2
get value 15 from ch2
get value 16 from ch2
get value 7 from ch1
get value 8 from ch1
get value 9 from ch1
get value 17 from ch2
get value 18 from ch2
get value 19 from ch2
time out

当然每次运行结果都可能不一样,因为 goroutine 调度的不确定性。

上述代码中,我们通过select多路复用分别从chl和ch2中读取数据,如果多个case语句中的ch同时到达,那么select将会运行一个伪随机算法随机选择一个case。在最后的case中我们设定可接受的最大时长为2s,如果时间超过2s,将从select中退出。

由于channel的阻塞是无法被中断的,所以这是一种有效地从阻塞的channel中超时返回的小技巧。

sync包

go中除了使用channel进行goruntine之间的同步和通信操作外,还可以使用sync包提供的并发工具类主要有7种:

  • Mutex 互斥锁
  • RWMutex 读写锁
  • WaitGroup 并发等待组
  • Map 并发安全字典
  • Cond 同步等待条件
  • Once 只执行一次
  • Pool 临时对象池

这节讲解一下互斥锁的用法:sync.Mutex能够保证同一个时间段内只有一个goruntine持有锁,这就能保证在某一个时间段内有且仅有一个goruntine访问共享资源,其他想申请锁的goruntine将会被阻塞直到锁被释放,然后重新争抢锁的持有权

demo:使用Sync.Mutex控制多个goruntine串行执行

package main

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

func main() {

    var lock sync.Mutex

    go func() {
        //加锁
        lock.Lock()
        defer lock.Unlock()

        fmt.Println("func1 get lock at:" + time.Now().String())
        time.Sleep(time.Second)
        fmt.Println("func1 release lock at:" + time.Now().String())
    }()

    time.Sleep(time.Second / 10)

    go func() {
        lock.Lock()
        defer lock.Unlock()
        fmt.Println("func2 get lock at:" + time.Now().String())
        time.Sleep(time.Second)
        fmt.Println("func2 release lock at:" + time.Now().String())
    }()

    //等待所有goruntine执行完毕
    time.Sleep(time.Second * 4)
}

读写锁的用法:

  • 为了保证同一时间段有多个goruntine访问同一资源,要满足以下条件:
  • 同一时间段只能有一个goruntine获取到写锁
  • 同一时间段可以有任意多个goruntine获取到读锁
  • 同一时间段只能存在读锁或写锁(读锁和写锁互斥)

demo:sync.RWMutex 允许多读和单写的案例

package main

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


var rwLock sync.RWMutex

func main() {
    //获取读锁
    for i := 0; i < 5; i++ {
        go func(i int) {
            rwLock.RLock()
            defer rwLock.RUnlock()

            fmt.Println("read func" + strconv.Itoa(i) + " get rlock at:" + time.Now().String())
            time.Sleep(time.Second)
        }(i)
    }

    time.Sleep(time.Second / 10)

    //获取写锁
    for i := 0; i < 5; i++ {
        go func(i int) {
            rwLock.Lock()
            defer rwLock.Unlock()

            fmt.Println("write func" + strconv.Itoa(i) + " get wlock at" + time.Now().String())
            time.Sleep(time.Second)
        }(i)
    }
    //保证所有的goruntine执行结束
    time.Sleep(time.Second * 10)
}

运行结果:

read func4 get rlock at:2023-03-14 14:24:02.5203537 +0800 CST m=+0.003223301
read func1 get rlock at:2023-03-14 14:24:02.5203537 +0800 CST m=+0.003223301
read func2 get rlock at:2023-03-14 14:24:02.5203537 +0800 CST m=+0.003223301
read func3 get rlock at:2023-03-14 14:24:02.5203537 +0800 CST m=+0.003223301
read func0 get rlock at:2023-03-14 14:24:02.5203537 +0800 CST m=+0.003223301
write func0 get wlock at2023-03-14 14:24:03.5513528 +0800 CST m=+1.034222401
write func4 get wlock at2023-03-14 14:24:04.5535884 +0800 CST m=+2.036458001
write func2 get wlock at2023-03-14 14:24:05.5637904 +0800 CST m=+3.046660001
write func1 get wlock at2023-03-14 14:24:06.5747967 +0800 CST m=+4.057666301
write func3 get wlock at2023-03-14 14:24:07.5782438 +0800 CST m=+5.061113401

从输出的结果可以看出,在写锁没有被获取时,所有read goroutine可以同时申请到读锁,如read func0到read func4几乎在同一时间点获取到读锁;

而申请写锁的goroutine必须等到没有任何的读锁和其他写锁存在时才能申请成功,如write func0需要等到readfunc最后一个goroutine释放读锁才能申请到写锁,其他申请写锁的write func需要等待前一个write func释放写锁后才能重新争抢写锁,它们获取到写锁的时间大约相差1秒,这刚好是 每个write func 持有写锁的时间

demo:sync.WaitGroup使用

package main

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

/*
sync.WaitGroup使用:
使用sync.WaitGroup的goruntine会等预设好数量的goruntine都提交执行结束后,才会继续往下执行代码

*/

func main() {

   var waitGroup sync.WaitGroup

   //设置等待goruntine数量为5
   waitGroup.Add(5)
   for i := 0; i < 5; i++ {
      go func(i int) {
         fmt.Println("work" + strconv.Itoa(i) + " is done at " + time.Now().String())
         time.Sleep(time.Second)
         waitGroup.Done()
      }(i)

   }
   waitGroup.Wait()
   fmt.Println("all works are done at:" + time.Now().String())
}

demo:sync.Map的使用

package main

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

/*
sync.Map的使用:
Go语言中原生的Map并不是并发安全的,在多个goroutine同时往Map中添加数据时,可能会导致部分添加数据的丢失,为了避免这种情况,
在Go语言l.9版本之前,我们需要结合sync.RWMutex和Map实现并发安全的字典。
Go语言1.9之后提供了sync.Map,相对于原生的Map,它只提供以下接口:

*/

var syncMap sync.Map
var waitGroup sync.WaitGroup

func main() {

   routineSize := 5
   //设置等待goruntine数量为5
   waitGroup.Add(routineSize)

   //并发添加数据
   for i := 0; i < routineSize; i++ {
      go addNumber(i * 10)
   }

   //开始等待
   waitGroup.Wait()
   var size int

   //统计数量
   syncMap.Range(func(key, value interface{}) bool {
      size++
      fmt.Println("key-value pair is ", key, value)
      return true

   })
   fmt.Println("syncMap current size is " + strconv.Itoa(size))
   //获取键为0的值
   value, ok := syncMap.Load(0)
   if ok {
      fmt.Println("key 0 has value ", value, " ")
   }
}

func addNumber(begin int) {
   //往syncMap中放入数据
   for i := begin; i < begin+3; i++ {
      syncMap.Store(i, i)
   }

   //通知数据已添加完毕
   waitGroup.Done()
}

补充学习资料:关于golang的并发同步与安全 - 9ong (可以补充学习)

并发同步之sync.WaitGroup

golang经常会有多协程并行的场景,而我们又需要这些协程全部结束后,继续完成main协程的后续行为,这个时候,我们需要一个谁来统计所有协程的运行结果,并通知main协程,不能仅仅依靠timeout、sleep的方式硬编写main协程等待其他协程时间,无法完美预估其他协程最晚完成时间。

所以就有了sync.WaitGroup,sync.WaitGroup内部维护着一个计数器,计数器的值可以增加和减少。比如启动了5个并发任务时,就将计数器值增加5,每个任务完成时通过调用Done()方法将计数器减1,当计数器值为0时,表示所有并发任务已经完成,通过调用Wait()来等待并发任务执行完,达到main协程知道所有协程任务完成的效果。

package main

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup

func goodjob(num int) {
    defer wg.Done()
    fmt.Println(num, "good job.")
}
func main() {
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go goodjob(i)
    }
    fmt.Println("main goroutine do something.")
    wg.Wait()
    fmt.Println("main goroutine end.")
}

使用sync.WaitGroup后的输出:

main goroutine do something.
1 good job.
0 good job.
2 good job.
3 good job.
4 good job.
main goroutine end.

如果不使用WaitGroup,将不会看到goodjob协程的输出打印,只有main协程的输出,因为main协程并不会去等待其他协程,而是先执行并退出main协程,一旦退出main协程,其他协程也会被销毁(在销毁前还来不及执行,因为需要一些时间给GMP调度)

并发加载之sync.once

可能不同协程会加载相同的静态资源,在高并发场景下,这些资源其实只需要加载一次就可以,比如配置、关闭一次通道、连接远端资源等,sync包提供了Once解决方案,一次只执行一次的解决方案。

package main

import (
    "errors"
    "fmt"
    "sync"
)

type subConfig struct {
    url  string
    src  string
    desc string
}

var configs map[string]subConfig
var loadOnce sync.Once

func loadConfigs() {

    configs = map[string]subConfig{
        "icons":   loadIcons(),
        "ads":     loadAds(),
        "banners": loadBanners(),
    }
}

func getConfig(key string) (res subConfig, err error) {
    //这里如果我们判断nil的方式来规避并发的话,是不安全的
    // if configs == nil {
    //  loadConfigs()
    // }
    //所以我们换成了sync.Once的Do方法来控制并发安全及避免通过锁的机制来控制资源并发访问带来的性能问题
    loadOnce.Do(loadConfigs)
    res, ok := configs[key]
    if !ok {
        errors.New("key invalid.")
    }
    return
}

func loadIcons() subConfig {
    //可能从文件、缓存、数据库等资源获取
    icons := subConfig{
        src:  "http://www.9ong.com/xxx.png",
        url:  "http://www.9ong.com/",
        desc: "9ong icon desc",
    }

    return icons
}

func loadBanners() subConfig {
    //可能从文件、缓存、数据库等资源获取
    banners := subConfig{
        src:  "http://www.9ong.com/xxx.png",
        url:  "http://www.9ong.com/",
        desc: "9ong banner desc",
    }

    return banners
}

func loadAds() subConfig {
    //可能从文件、缓存、数据库等资源获取
    ads := subConfig{
        src:  "http://www.9ong.com/xxx.png",
        url:  "http://www.9ong.com/",
        desc: "9ong ads desc",
    }

    return ads
}

func main() {
    //并发调用getConfig
    ads, err := getConfig("ads")
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(ads)
    }
}

在getConfig函数中,我们通过判断configs这个全局变量是否为nil,决定是否重新加载configs数据,在这种情况下就会出现即使判断了configs不是nil,也不意味着configs变量初始化完成了。考虑到这种情况,我们能想到的办法就是添加互斥锁,保证初始化加载configs全局资源变量的时候不会被其他的协程读写,但是这样做又会引发性能问题。

sync.Once内部包含一个互斥锁和一个布尔值,互斥锁保证布尔值和数据的安全,而布尔值用来记录初始化是否完成。这样设计就能保证初始化操作的时候是并发安全的并且初始化操作也不会被执行多次。

所以getConfig函数改造后,通过loadOnce.Do(loadConfigs)来实现互斥加载。

解决了并发加载问题,还不会因为多次加载浪费时间和空间及引入锁等机制带来的性能问题。

并发安全与锁之sync.mutex

package main

import (
    "fmt"
    "sync"
)

var love int32 = 10000 //公共资源love
var wg sync.WaitGroup

func getLove() {
    defer wg.Done()
    for i := 0; i < 1000; i++ {
        love = love - 1
    }
    fmt.Println("love left:", love)
}
func main() {
    for children := 0; children < 10; children++ {
        wg.Add(1)
        go getLove()
    }
    wg.Wait()
    fmt.Println("love is:", love)
}

由于启动了10个协程,这些协程存在竞争love资源的可能(多试几次或把初始值和循环数调大点),可能会出现以下的输出,其实也就是电商里说的超卖问题,按照逻辑love最后应该为0,但实际情况是love还剩下3045:

I:\src\go\src\helo>go run test.go
love left: 7763
love left: 8763
love left: 9000
love left: 6763
love left: 6452
love left: 5452
love left: 5220
love left: 4220
love left: 4045
love left: 3045
love is: 3045

超卖的问题在于并发不安全,并发时对临界资源的读写不安全,我们需要考虑在读写临界资源时,将并发/并行转换成串行,通常采用加锁的机制。

互斥锁(完全互斥)是一种常用的控制共享资源访问的方法,它能够保证同时只有一个协程可以访问共享资源。golang中使用sync包的Mutex类型来实现互斥锁:

package main

import (
    "fmt"
    "sync"
)

var love int32 = 10000 //公共资源love
var wg sync.WaitGroup
var mu sync.Mutex

func getLove() {
    defer wg.Done()
    mu.Lock()
    for i := 0; i < 1000; i++ {
        love = love - 1
    }
    mu.Unlock()
    fmt.Println("love left:", love)
}
func main() {
    for children := 0; children < 10; children++ {
        wg.Add(1)
        go getLove()
    }
    wg.Wait()
    fmt.Println("love is:", love)
}

前面我们说互斥锁是完全互斥的,但是有很多实际的场景下是读多写少的,当并发去读取一个资源不涉及资源修改的时候是没有必要加锁的,如果使用完全互斥锁的话,会导致读多的协程会堵塞等待锁的释放,很影响效率,这种场景下使用读写锁是更好的一种选择。读写锁在Go语言中使用sync包中的RWMutex类型。

读写锁分为读锁和写锁,当一个协程获取读锁之后,其他的协程如果是获取读锁会继续获得锁,如果是获取写锁就会等待;当一个协程获取写锁之后,其他的协程无论是获取读锁还是写锁都会等待。

也就是说,读写锁互斥,读读不会互斥,协程的读锁,不应其他协程的读锁获取,但影响写锁的获取;而写锁影响所有协程对该资源锁的获取。

编码如何处理呢?

我们只要对共享资源的读加读锁Rlock(),对共享资源的写加写锁lock()

var rwlock sync.RWMutex

func read(){
    rwlock.RLock()
    //read love action
    rwlock.RUnlock()
}

func write(){
    rwlock.Lock()
    //writen love actioin
    rwlock.Unlock()
}

并发安全之sync.map

golang内置map不是并发安全的,很好理解,map只是一种资源的数据结构而已,所以作为共享资源,其本身当然也是并发不安全的,对于map中key、value的操作也需要做并发安全处理,比如加锁。

虽然加锁可以解决,但golang也提供了一套并发安全的map操作类:sync.Map,其内置了Store、Load、LoadOrStore、Delete、Range等操作方法,这些方法都是并发安全的,可以理解为原子操作。

package main

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

var sm = sync.Map{}
var wg = sync.WaitGroup{}

func setMap(i int) {
    key := strconv.Itoa(i)
    sm.Store(key, i)
    value, _ := sm.Load(key)
    fmt.Printf("k=:%v,v:=%v\n", key, value)
    wg.Done()

}

func main() {
    for i := 0; i < 20; i++ {
        wg.Add(1)
        go setMap(i)
    }
    wg.Wait()
}

并发安全之atomic

针对基本数据类型我们还可以使用原子操作来保证并发安全,因为原子操作是Go语言提供的方法它在用户态就可以完成,因此性能比加锁操作更好。Go语言中原子操作由内置的标准库sync/atomic提供。

package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

var love int32 = 10000 //公共资源love
var wg sync.WaitGroup
var mu sync.Mutex

func getLove() {
    defer wg.Done()
    for i := 0; i < 1000; i++ {
        love = love - 1
    }
    fmt.Println("love left:", love)
}
func mutexGetLove() {
    defer wg.Done()
    mu.Lock()
    for i := 0; i < 1000; i++ {
        love = love - 1
    }
    mu.Unlock()
    fmt.Println("love left:", love)
}
func atomicGetLove() {
    defer wg.Done()
    for i := 0; i < 1000; i++ {
        atomic.AddInt32(&love, -1)
    }
    fmt.Println("love left:", love)
}
func main() {
    for children := 0; children < 10; children++ {
        wg.Add(1)
        // go getLove() //普通,并发不安全
        // go mutexGetLove() //互斥锁,并发安全,但性能开销大
        go atomicGetLove() //原子操作,并发安全,性能优于互斥锁机制
    }
    wg.Wait()
    fmt.Println("love is:", love)
}

atomic包提供了底层的原子级内存操作,虽然其对于同步算法的实现很有效果,但除了某些特殊的底层应用,我们建议使用channel或者sync.包实现同步会更好些。

golang中的map并发读写问题: Golang 协程并发使用 Map 的正确姿势 map 不是并发安全的 官方的faq里有说明,考虑到有性能损失,map没有设计成原子操作,在并发读写时会有问题。 Map access is unsafe only when updates are occurring. As long as all goroutines are only reading—looking up elements in the map, including iter... 阅读详情

相关推荐

电驱系统功率级“多合一”解决方案

如何提升电驱动系统的产品性能、减小能耗和进一步轻量化,以加强整车市场竞争力,是摆在国内外新能源车企面前的共同课题。为此,具有多年动力总成电子系统开发经验的美国著名半导体及电子解决方案专家——德州仪器(Texas Instrument)提出了多种功率级别“多合一”整合方案和设计思路,助力新能源车企提升技术优势。

yessunday的专栏 1370

Go语言并发详解

文章目录一、Go语言并发简述(并发的优势)进程/线程并发/并行协程/线程Goroutine 介绍channel二、Go语言goroutine(轻量级线程)使用普通函数创建 goroutine1) 格式2) 例子使用匿名函数创建goroutine1) 使用匿名函数创建goroutine的格式2) 使用匿名函数创建goroutine的例子提示三、Go语言并发通信四、Go语言竞争状态简述Goroutine 7 (finished) created at: main.main() D:/code/src/m

qq_41854911的博客 1万+

DOORS需求管理实战:从汽车设计案例看模块化需求拆分技巧

本文通过一个汽车设计实战案例,深入解析了如何利用DOORS进行模块化需求拆分与管理。从捕获原始用户需求到精炼为可追踪的系统需求,再到按子系统(如自动驾驶)进行模块化拆分,文章详细展示了构建需求骨架、建立可追溯性以及实施变更控制的完整流程,帮助团队在复杂系统工程中实现从混沌到秩序的高效协同。

ww90123的博客 242

Golang通道(chan,协程交互数据)

通道(channel)是用来传递数据的一个数据结构。通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符

墨痕诉清风的博客 4071

激光雷达接收芯片选型指南:APD、SiPM、SPAD阵列的优缺点对比

本文深入对比了激光雷达接收芯片APD、SiPM和SPAD阵列的核心技术原理与性能差异。APD作为线性模式工作的稳健选择,动态范围大;SiPM具备单光子探测和高增益优势;而SPAD阵列凭借像素级数字读出和集成TDC,在时间分辨率与系统集成度上领先。文章从灵敏度、时间精度、动态范围及系统成本等多维度剖析,为不同应用场景(如车载雷达、Flash LiDAR、消费电子)的芯片选型提供实战指南。

ik6789012的博客 655

RK3568平台开发系列讲解(NPU篇)什么是RKNPU

📢本篇章将介绍RKNPU。

内核笔记 4354

Go并发编程

并发介绍 进程和线程 A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。 B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。 C.一个进程可以创建和撤销多个线程;同一个进程中的多个线程之间可以并发执行。 协程和线程 协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户级线程的调度也是自己实现的。线程:一个线程上可以跑多个协程,协程是轻量级的线程...

上山的人 2109

YOLO11数据增强中的色彩空间变换:HSV、YUV等增强技术的原理、实现与参数调优全解析

摘要 本文介绍了色彩空间的基本概念及其在数据增强中的应用,重点分析了HSV色彩空间的原理与特性。色彩空间作为颜色表示的数学模型,在计算机视觉中具有重要作用,RGB、HSV、YUV等常用色彩空间各有特点。数据增强中的色彩变换能提升模型鲁棒性、缓解数据不平衡并防止过拟合。HSV色彩空间将颜色分为色调(H)、饱和度(S)和明度(V)三个独立分量,更符合人类视觉感知,便于针对性调整图像属性。文章详细阐述了HSV与RGB的转换公式,为YOLO11等目标检测框架的数据增强提供了理论基础。

本博客聚焦 YOLOv11 全流程落地,涵盖架构优化、数据集处理、训练技巧与多场景部署,兼及国产数据库、Java 开发与 AI 模型应用,内容兼顾理论与工程实践,为开发者提供系统干货,助力高效提升技术能力。 286

Go语言实战-第六章 并发

通常程序以独立方式编写,简单易维护,但一些时候处于性能考量就需要并发,go语言的并发是基于csp模型的,通信顺序进程(Communicating Sequential Processes,CSP),不像其他语言对数据加锁,而是通过使用通道channel在gorountine中传递信息。

qq_39167720的博客 1244

OmniBox2.0.X版本 一站式影视订阅与多端播放平台文档

OmniBox 是一站式影视订阅与多端播放平台:你可以用 JS 或 Python 按 OmniBox 规范自建爬虫源,在后台把多个爬虫源自由组合成订阅,然后在网页、手机、电视等全平台的客户端里使用。只要客户端支持 TVBox、猫源、UZ、Emby 等协议,即可无缝对接。同时支持电视直播源、六大平台直播间和网盘源视频在线观看,通过 Docker 一键部署。

欢迎来到鸾姝淡月博客,专为程序员和编程爱好者打造的技术博客。我们分享编程语言趋势、开发工具、编程哲学、职业发展等,助你掌握新技术,优化技能,激发编程热情。 1893

关于如何在matlab中导入并翻译Hypemesh导出的大型刚度矩阵txt文本

如何将它翻译成matlab可以使用的矩阵变量,并参与运算。 由于这个大型刚度矩阵125万x125万≈1e12。matlab甚至其他计算软件无法保存这个大的一个矩阵文件。而这个刚度矩阵作为稀疏矩阵其中非零元素的数目只有有500多万。采用sparse稀疏矩阵技术是可以保存。 这里简单介绍一下这个Hypemesh导出的大型刚度矩阵txt文本。如图DMIG开头的标识符是换行符,后面两列是表示这条非零元素在刚度矩阵中列数的信息。我们已经将左边字母和符号等批量删除,得到了右边textread函数可以...

wenyusuran的专栏 1795

Go 并发编程

关于 Go 语言之高性能并发编程的基础知识!

qq_46457076的博客 1580

【UG\NX二次开发】多个带ui的dll怎么整合到一个dll

NX二次开发-群精选」专栏收录了那些曾散落在聊天对话框里的精彩的知识分享,这都是被遗落的宝贵财富。让我们一起向大佬学习,充满激情地进步,不断超越自己。底部联系方式,欢迎您加入一起交流。有大佬知道多个带ui的dll怎么整合到一个dll吗。

王牌飞行员_里海的博客 283

【Golang】Go语言中的并发模式

这些模式是编写Go语言并发程序的基础。每种模式都有其使用场景、优点和缺点,您可能会发现结合多种模式可以为您的应用程序提供最佳解决方案。仔细考虑应用程序的具体需求,并选择合适的模式。

qq_40205510的博客 223

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

进一步认识golang中的并发

如果你成天与编程为伍,那么并发这个名词对你而言一定特别耳熟。需要并发的场景太多了,例如一个聊天程序,如果你想让这个聊天程序能够同时接收信息和发送信息,就一定会用到并发,无论是那是什么样的并发。 并发的意义就是:让一个程序同时做多件事情! 理解这一点非常重要,是的,并发的目的只是为了能让程序同时做另一件事情而已,并发的目的并不是让程序运行的更快(如果是多核处理器,而且任务可以分成相互独立

闷骚的Gopher 7049

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

Go 中的并发性 (Golang)

通道发送和接收是如何工作的?使用 Context Package.Bonus Section 的 HTTP 服务器超时 - 接口如何将不同对象之间的共同行为定义为抽象类型?第 15 讲 深入探究 - Go Scheduler - 由于同步系统调用而导致的上下文切换。第 16 讲 深入探究 - Go Scheduler - 由于异步系统调用而导致的上下文切换。第 28 讲 深入探究 - 通道 - 发送和接收。第 29 讲:深入探究 - 通道 - 缓冲区已满。第 30 讲:深入探究 - 通道 - 缓冲区为空。

jianqimingtian的博客 585

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

golang中的并发模型

传统的编程语言(如C++、Java、Python等)并非为并发而生的,因此它们面对并发的逻辑多是基于操作系统的线程。其并发的执行单元(线程)之间的通信利用的也是操作系统提供的线程或进程间通信的原语,比如共享内存、信号、管道、消息队列、套接字等。在这些通信原语中,使用最多、最广泛同时也最高效的是结合了线程同步原语(比如锁以及更为低级的原子操作)的共享内存方式,因此,可以说传统语言的并发模型是基于共享内存的模型。

m0_73976305的博客 1039

Golang 中的 slice 为什么是并发不安全的?_go slice为什么并发不安全

在Go语言中,slice是并发不安全的,主要有以下两个原因:数据竞争、内存重分配。数据竞争:slice底层的结构体包含一个指向底层数组的指针和该数组的长度,当多个协程并发访问同一个slice时,有可能会出现数据竞争的问题。例如,一个协程在修改slice的长度,而另一个协程同时在读取或修改slice的内容。内存重分配:在向slice中追加元素时,可能会触发slice的扩容操作,在这个过程中,如果有其他协程访问了slice,就会导致指向底层数组的指针出现异常。

2401_85236365的博客 1005

Golang深入浅出之-Go语言中的并发模式:Pipeline、Worker Pool等

掌握Pipeline和Worker Pool模式,是深入理解Go并发编程的关键。在实际应用中,合理设计并发模式可以显著提升程序的性能和响应速度。但同时,也要警惕数据竞争、死锁等问题,通过恰当的数据同步机制和任务管理策略来规避风险。实践出真知,动手编写并测试代码,是掌握并发编程艺术的最佳途径。

zevjay的博客 678

golang mysql并发_进一步认识golang中的并发

如果你成天与编程为伍,那么并发这个名词对你而言一定特别耳熟。需要并发的场景太多了,例如一个聊天程序,如果你想让这个聊天程序能够同时接收信息和发送信息,就一定会用到并发,无论那是什么样的并发。并发的意义就是:让一个程序同时做多件事情!理解这一点非常重要,是的,并发的目的只是为了能让程序同时做另一件事情而已,并发的目的并不是让程序运行的更快(如果是多核处理器,而且任务可以分成相互独立的部分,那么并发确...

weixin_32667011的博客 795

Golang深入浅出之-Go语言中的并发安全队列:实现与应用

在Go语言中,并发编程是其核心特性之一,而并发安全的数据结构则是构建高性能并发系统的基础。本文将深入探讨Go语言中的并发安全队列,包括其实现原理、常见问题、易错点及避免策略,并通过代码示例加以说明。

zevjay的博客 933

Go语言中的并发

简单介绍go中的并发编程. 涉及内容主要为goroutine, goroutine间的通信(主要是channel), 并发控制(等待、退出).

weixin_42236936的博客 1335

Golang深入浅出之-Select语句在Go并发编程中的应用

select语句是Go语言特有的语法结构,专门用于协调多个通道(channel)的读写操作。在一个select语句中,可以列出多个case,每个case对应一个通道操作(发送或接收)。当select执行时,它会阻塞并等待所有列出的通道操作中至少有一个变得可行(即,对于接收操作,通道中有数据可读;对于发送操作,通道有足够的容量可写入数据)。一旦某个操作变得可行,select就会执行该case对应的代码块,并可能传递数据(对于接收操作)或接收数据(对于发送操作)。如果所有case都无法立即执行,且select。

zevjay的博客 516

Golang中的CSP并发机制

Flutter、Golang、Python、编译原理、算法、Chrome原理学习系列文章抢先看请关注【码农帮派】: 【Golang学习系列文章,请扫二维码】 CSP是Go语言中特有的一种并发控制机制,相比于共享内存并发控制机制更为高效。CSP是Communicating Sequential Process的缩写。 CSP的设计理念,是依赖一个信息通道来完成两个通信实体之间的通信协...

天马行空的专栏 1443

Golang深入浅出之-Go语言中的CSP模型:深入理解并发哲学

Go语言的CSP模型通过goroutines和channels,提供了一种简洁、高效的并发编程方式。理解CSP的核心思想,避免诸如数据竞争、死锁和goroutine泄漏等常见问题,是编写高质量并发Go程序的基础。通过实践和深入理解这些概念,开发者可以构建出既高性能又易于维护的并发系统。

zevjay的博客 393
上一篇: 错误与异常处理
下一篇: go读取文件的几种方法
Cc_sec
博客等级 码龄8年 6粉丝 42原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值