协程可以算是go语言的最大特点,也是go诞生的初衷

很多文章有这么一句话叫做,一核有难多核围观,意思是针对多核CPU一个核非常忙,剩下的核确是闲置的,出现上面的原因是写的程序是单核处理器,不是针对多核CPU的高并发程序,在这里也记录一些并发和并行的区别,借用看到的资料的解释,比较形象,并发就是使用同一个锅炒不同的菜,菜品在锅中随时切换,并行就是有多个锅,每个锅同时炒不通的菜,并发就是你拿一把刀切菜,一会切白菜,一会切萝卜,一会切茄子,你的这把刀就是CPU的一个核,然后并发的去切很多菜,并行就是你用多把刀同时切不同的菜

注意:并发不是并行,并行是让不同的代码片段同时在不同的物理处理器上执行,并行的关键是同时做很多事情,而并发是指同时管理很多事情,这些事情可能只做了一般就被暂停去做别的事情了在很多情况下,并发的效果比并行好,因为操作系统和硬件的总资源一般很少,但能支持系统同时做很多事情

协程实现和协程交互

func testfun() {
    // do something
}
go testfun()

这种用法还是相当方便的,只需要加一个关键字就可以实现协程的功能,但是这种一般都是独立的协程,如果需要协程之间相互通信,go语言也提供了多种方法,第一种就是跟C/C++一样的加锁

lock.Lock()
testname = "newname" 
lock.Unlock()

但是go语言虽然支持这种锁的方式进行线程之间的通信,但是go一般不用这种方式,go一般都用通道channel的形式来进行协程之间的数据交互,

创建和使用channel

channel是一个通道,队列,那么我们关系的应该是如果创建这个通道,将数据装进去,从通道中提取数据,golang为他设计了一个操作符

left <- right, 当left为channel时,表示向通道中写入数据,并且如果通道存在数据,写入会被阻塞,所有可以简历一个大小为n的通道,当写入的数量大于n时,通道才会阻塞,当right为通道时,表示从通道通提取数据

package main
import "fmt"
func main() {
    simpleChan()
}

func simpleChan () {
    // 申明chan类型的字符串变量
    var ch chan string
    ch = make(chan string)
    // 向channel中发送string类型数据
    go func(){
        ch <- "ping"
    }()
    // 创建一个string类型的变量,用来保存从channel队列提取的数据
    var v string 
    v = <-ch
    fmt.Println(v)
}

上面的例子中创建了一个通道ch,然后make了一块内存,这个通道实现了一个类似队列的功能,有数据写入,里面如果没有被读走,就排队等候,上面代码里面的两个操作语句就可以完成了数据入队列(ch <- "ping"),数据出队列(v = <-ch)的动作,这里有个问题需要注意,channel的接收宇发送需要分别在两个goroutine中,如果你是直接看英文的文档,或者其他介绍的文章,可能没有指出这个要求,他是垮协程的,如果 ch <- "ping"不用协程调用,会报错

从上面的例子可以看到协程通过通道ch来实现数据传递,这个通道ch就类似枷锁操作的变量,通道也是一种数据结构,跟使用枷锁方式操作变量一样,通道定义的时候也需要定下来通道的类型,定义好后不能修改

数据协程,通道channel使用实例

这个例子是我在搜索资料看到的,感觉还行,放在记录一下,例子中主要展示的原意是有两个干活的worker, 然后有5个工作的job,需要这两个人来完成5个工作,功能实现里面还定义了五个工作完成的结构

package main
import (
    "fmt"
    "time"
)
func main() {
    workpools()
}
func workpools() {
    const numberOfJobs = 5
    const numberOfWorkers - 2
    jobs := make(chan int, numberOfJobs)
    results := make(chan string, numberOfJobs)
    // 向任务队列写入任务
    for i := 1; i <= numberOfJobs; i++ {
        jobs <- i
    }
    fmt.Println("布置job后,关闭jobs channel")
    chose(jobs)
    // 控制并行度,每个worker函数都运行在单独我goroutine中
    for w := 1; w <= numberOfWorkers; w ++ {
        go worker(w, jobs, result)
    }
    // 监听results channel,只要有内容就会被取走
    for i := 1; i <= numberOfJobs; i++ {
        fmt.Printf("结果: %s\n", <-result)
    }
}

// worker 逻辑: 一个不断从jobs chan中取任务的循环
// 并将结果放在out channel中待取
func worker(id int, jobs <-chan int, out chan<- string) {
    fmt.Printf("worker #%d 启动\n", id)
    for job := range jobs {
        fmt.Printf("worker #%d 开始工作%d\n", id, job)
        // sleep 模拟 [正在处理任务]
        time.Sleep(time.Millisecond * 500)
        fmt.Print("worker #%d 工作%d", id, job)
        out <- fmt.Sprintf("worker #% 结束工作 %d\n", id, job)
    }
    fmt.Printf("worker #%d 退出\n", id)
}

在go语言中,协程(goroutine)是指在后台中运行的轻量级执行线程,go协程是go中实现并发的关键组成部分,go中提供了一个关键字go来创建go协程,当在函数或方法的调用之前添加这个go关键字,这样就开启了一个go的协程,该函数或方法就会在这个go的协程中运行

由于go协程相对于传统的操作系统的中线程是非常轻量级的,由此对于一个典型的go应用来说,有数以千计的go协程并发运行的情形是非常常见的,并发可以显著地提升应用的运行速度,并且可以帮助我们编写关注点分离的代码

什么是go的协程

我们也许在理论上已经知晓go协程是如何工作的,但在代码层面上,go协程是何须物也,其实,go协程看起来只是一个与其他众go协程并发运行的一个简单函数或者方法, 但是我们并不能想当然地从函数或者方法中定义来确定一个go的协程,go协程的确定还是要取决于我们如何去调用

go中提供了一个关键字go来让我们创建一个go的协程,当我们在函数或方法的调用之前添加一个关键字go,这样我们就开启了一个go的协程,该函数或者方法就会在这个go协程中运行

进程

进程是应用程序的启动实例,是系统进行资源分配和调度的基本单位,每个进程都有独立的内存空间,不通进程通过进程间的通信方式来通信

线程

线程从属于进程,是进程的一个实体,线程是CPU调度的基本单位,一个线程由线程ID,当前指令指针,寄存器集合和堆栈组成

小城不拥有自己的系统资源,他与同属于同一进程的其他线程共享进程所拥有的全部资源,多个线程之间通过共享内存等线程间的通信方式来通信,线程拥有自己独立的站和共享的堆

协程

协程可以理解为轻量级线程,一个线程可以拥有多个协程,与线程相比,协程不受操作系统调度,协程调度器按照调度策略把协程调度到线程中执行,协程调度器由应用程序runtime包提供,用户使用go关键字即可创建协程,这也就是go在语言层面直接支持协程的含义

协程的优势

由于协程运行在用户态,能够大大减少上下文切换带来的开销,并且协程调度器把可运行的协程逐个调度到线程中执行,同时及时把阻塞的协程调度出线程,从而有效的避免了线程的频繁切换,达到了使用少量线程实现高并发的效果,但对于一个线程来说每一时刻只能运行一个协程

进程,线程 协程对吧

1.协程既不是进程也不是线程,一个进程可以包含多个线程,一个线程可以包含多个协程,进程,线程,协程不是同一个维度的

2.进程,线程的切换者都是操作系统,切换时机由操作系统的切换策略决定,而协程的切换者是用户,切换时间由用户自己的程序来决定

3.进程的切换内容包含页全局目录,内核栈,硬件上下文,切换内容保存在内存中

4.切菜的切换过程只存在于用户态,因此切换效率高

go协程是与其他函数或方法一起并发运行的函数或方法,go协程可以看做是轻量级的线程,与线程相比,穿点一个go的协程成本很小,因此在go应用中,常常会看到有数以千计的go协程并发运行

启动一个go协程

调用函数或者方法时,如果在前面加上关键字go,就可以让一个新的go协程并发地运行


花落雨忧
1 声望0 粉丝