Thread vs Groutine

1、创建时默认的stack的大小

JDK5以后JAVA Thread stack默认为1M

Groutine的Stack初始化为2K

2、和KSE(Kernel Space Entity)的对应关系

Java Thread是1:1

Groutine是M:N

JAVA的线程对应一个内核对象,一个线程的时候CPU调度的效率非常高,但是多个线程切换的时候需要切换内核对象,对比起来,go的多个线程对应一个内核对象,因此go的线程切换效率特别高

3、协程并发处理的机制

上图中,M代表的是系统线程,P是GO语言自己实现的协程处理器,G是一个协程任务,组成协程队列。

如果一个协程花费的时间特别长

机制1:协程的守护进程会记录协程处理器完成的协程数量,如果一段时间内,处理器完成的数量没有变化,就会往任务栈中插入一个标记,当前协程遇到非内联函数时,在任务栈中读到该标记,就会把该协程任务移到队列的末尾;

机制2:如果执行协程过程中遇到一个非CPU密集型任务,例如IO,需要中断等待时,协程处理器P会把自己移到另外一个可使用的系统线程中,继续执行队列中其他的协程任务。当中断的协程被重新唤醒后,会把自己重新加入到协程队列里或者全局等待队列中。其中,中断的协程会把context保存在协程对象里,当协程被唤醒后会将其重新读取到寄存器中,继续运行。

4、GO实现的协程的例子

func TestGroutine(t *testing.T) {
    for i := 0; i < 10; i++ {
        go func(i int) {
            fmt.Println(i)
        }(i)
    }
    time.Sleep(time.Millisecond * 50)
}

一个错误的协程示例:

func TestGroutine(t *testing.T) {
    for i := 0; i < 10; i++ {
        go func() {
            fmt.Println(i)
        }()
    }
    time.Sleep(time.Millisecond * 50)
}

错误的原因:i作为一个共享变量,如果不加锁机制,打印出来一定与预期不符,而正确的写法里面i是一个整形,会以值传递给协程后,被深度拷贝了一份,不会被其他的协程修改,所以可以被正确执行

如果对上面的错误进行加锁:

func TestGroutine(t *testing.T) {
    var mutex sync.Mutex
    for i := 0; i < 10; i++ {
        go func() {
            defer func(){
                mutex.Unlock()
            }
            mutex.Lock()
            fmt.Println(i)
        }()
    }
    time.Sleep(time.Millisecond * 50)
}

也与预期结果一致
换成更高效的写法:

func TestGroutine(t *testing.T) {
    var mutex sync.Mutex
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        go func() {
            defer func(){
                mutex.Unlock()
            }
            mutex.Lock()
            fmt.Println(i)
            wg.Done()
        }()
    }
    wg.Wait()
}

准确的等待所有的协程执行完就退出,比傻傻的等好的多

你可能感兴趣的:(协程)