zoukankan      html  css  js  c++  java
  • Go

    进程和线程的说明
        1.进程就是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位。    
        2.线程是进程的一个执行实例,是程序执行的最小单元,它是比进程更小的能独立运行的基本单位。
        3.一个进程可以创建和销毁多个线程,同一个进程中的多个线程可以并发执行。
        4.一个程序至少有一个进程,一个进程至少有一个线程。
     
     
    并发和并行
        1.多线程程序在单核上运行,就是并发。
        并发
            因为是在一个cpu上,比如有10个线程,每个线程执行10毫秒(进行轮询操作),从人的角度看,好像这10个线程都在运行,但是从微观上看,在某一个时间点看,其实只有一个线程在执行,这就是并发。
        2.多线程程序在多核上运行,就是并行。
        并行
            因为是在多个cpu上(比如有10个cpu),比如有10个线程,每个线程执行10毫秒(各自在不同cpu上执行),从人的角度看,这10个线程都在运行,但是从微观上看,在某一个时间点看,也同时有10个线程在执行,这就是并行。
     
     
    Go协程和Go主线程
        1.Go主线程(也有直接称为线程/进程):一个Go线程上,可以起上万个协程,协程是轻量级的线程(底层是编译器做了优化)。
        2.Go协程的特点:
            有独立的栈空间
            共享程序堆空间
            调度由用户控制
            协程是轻量级的线程
        
        快速案例:
            package main 
            import (     "fmt"     "strconv"     "time" ) 
            func g_test() {     
                    for i:=0; i < 100; i ++ {         
                            fmt.Println("g_test() hello,world " + strconv.Itoa(i))         
                            // 隔一秒         
                            time.Sleep(time.Second)     
                    } 
            } 
            func main() {     
     
                    go g_test()  // 开启一个协程    
     
                    for i:=0; i < 10; i ++ {         
                            fmt.Println("main() hello,golang " + strconv.Itoa(i))         
                            time.Sleep(time.Second)     
                    } 
            }
            案例小结:
                1.主线程是一个物理线程,直接作用在cpu上,是重量级的,非常耗费cpu资源。
                2.协程从主线程开启的,是轻量级的线程,是逻辑态,对资源耗费相对小。
                3.Golang的协程机制是重要的特点,可以轻松的开启上万个协程,其他编程语言的并发机制是一般基于线程的,开启过多的线程,资源耗费大,这里就突显Golang在并发上的优势了。
     
     
    goroutine的调度模型
        MPG模式基本介绍:
            M:操作系统的主线程(是物理线程)
            P:协程执行需要的上下文
            G:协程
     
     
    Golang设置运行的cpu个数
        代码实现:
            package main 
            import (     "fmt"     "runtime" ) 
            func main() {     
                    cpuNum := runtime.NumCPU()     
                    fmt.Println("win cpuNum=", cpuNum)     
                    // 可以自己设置使用几个cpu     
                    res := runtime.GOMAXPROCS(cpuNum-2)     
                    fmt.Println("ok", res) 
            }
            go1.8后,默认让程序运行在多个核上,可以不用设置了。
            go1.8前,还是要设置一下的,可以更高效的利用cpu
     
     
    全局变量互斥锁解决资源竞争
        协程并发(并行)出现资源竞争问题:
            运行时增加 -race 参数,确实会发现有资源竞争问题(先编译:go build -race  main.go  然后执行main.exe文件,会出现资源竞争问题)
            sync包里的Mutex 里面的 Lock 和Unlock 方法来解决
            实际案例:
                package main 
                import (     "fmt"     "time"     "sync" ) 
                // 全局互斥锁解决 资源竞争问题 
                var (     
                    testMap = make(map[int]int)  
       
                    // 声明一个全局互斥锁     
                    // lock 是一个全局的互斥锁     
                    // sync 是包 :synchornized 同步     
                    // Mutex:互斥     
                    lock sync.Mutex 
                ) 
                func jc_test(n int) {     
                        res := 1     
                        for i := 1; i <= n; i ++ {         
                                res *= i     
                        }     
                        fmt.Println("n! ===", n , res)     
                        // 加锁     
                        lock.Lock()     
                        testMap[n] = res     
                        // 解锁     
                        lock.Unlock() 
                } 
                func main() {     
                        // 使用循环来产生多个协程     
                        for i:=1; i <= 200; i++ {       
                                // 开启200个协程  
                                go jc_test(i)     
                        }     
                        // 为了要执行协程内容 ,需要主线程中等待,不然会直接退出程序     
                        time.Sleep(time.Second * 10)          
                        // 打印出每个元素     
                        lock.Lock()     
                        for i, v := range testMap {         
                                fmt.Printf("testMap[%d]==%d  ", i, v)     
                        }     
                        lock.Unlock()     
                        // 按理说主线程等10秒 上面写的协程都应该执行完毕,后面读数据不应该出现资源竞争问题了     
                        // 但实际运行中还是会出现资源竞争的     
                        // 在读的地方加互斥锁,是因为我们程序设计上可以知道10秒执行完所有协程,但是主线程并不知道     
                        // 因此底层可能任然出现资源争夺,因此加入互斥锁即可解决问题 
                }
                上面的代码有个很大的问题:即我们主线程具体要等多久才合适呢?即引出了管道来实现协程间的通信;
     
     
    channel(管道)基本介绍
        上面使用全局变量加锁同步来解决goroutine的通讯,但不是完美的解决方案;通讯机制--channel
        channel的介绍:
            1.channel本质就是一个数据结构--队列
            2.数据是先进先出;[FIFO]
            3.线程安全,多goroutine访问时,不需要加锁,就是说channel本身就是线程安全的
            4.channel是有类型的,一个string的channel只能存放string类型数据;
        
        定义/声明channel:
            var  变量名  chan  数据类型
            var  intChan  chan  int  --> intChan用于存放int数据
            var  mapChan  chan  map[int]string -->mapChan用于存放map[int]string类型
            var  perChan  chan  Person
            var  perChan  chan  *Person
            说明:
                1.channel是引用类型
                2.channel必须初始化才能写入数据,即make后才能使用
                3.管道是有类型的,intChan 只能写入整数int;
        
        channel的基本使用:
            // 定义一个管道     
            var intChan chan int      
            intChan = make(chan int, 3)     
            // 看管道是什么?     
            fmt.Println("intChan--", intChan)  // intChan-- 0xc000104080     
            // 向管道写入数据     
            intChan<- 10     
            num := 20     
            intChan<- num      
            // 注意点:写入数据不能超过容量会报deadlock, 因为管道是用来一边写入一边取出的     
            intChan<- 30     
            // intChan<- 40     
            // 看管道的长度和容量(容量是定义时固定的不会动态增加)     
            fmt.Printf("intChan len=%v cap=%v  ", len(intChan), cap(intChan))  // intChan len=2 cap=3     
            // 从管道取出数据     
            var num2 int      
            num2 = <-intChan     
            fmt.Println("num2==",num2)     
            fmt.Printf("get intChan len=%v cap=%v  ", len(intChan), cap(intChan))      
            // 在没有使用协程的情况下,如果我们的管道数据已经全部取出,再取也会报deadlock     
            num3 := <-intChan     
            num4 := <-intChan     
            // num5 := <-intChan     
            fmt.Printf("num3==%d  num4==%d  ", num3, num4)
     
            // 当我们想放入任何类型的数据到管道内我们可以使用
            var  allChan  chan  interface{}
            // 但是我们获取时,取出的每个元素就是空接口,要使用类型断言来解决 即:
            cat := <-allChan
            yuan_cat := cat.(Cat)  // 此时的 yuan_cat 才是 Cat结构体,才能取出其中的属性值:yuan_cat.Name
     
        channel的关闭和遍历
            实际案例:
                // channel 的关闭    
                intChan := make(chan int, 10)     
                intChan <- 10     
                intChan <- 20     
                close(intChan)  // close 内置函数是用来专门关闭管道的     
                // intChan <- 30      
                // 关闭管道就不可以再向管道放数据了     
                fmt.Println("ok!!!")     
                // 但是可以获取数据     
                num := <- intChan      
                num2 := <- intChan      
                fmt.Println("num--", num, "num2--", num2)     
                // channel 的遍历 支持 for-range     
                // 在遍历时,如果channel没有关闭,则会出现deadlock的错误     
                // 在遍历时,如果channel已经关闭,则会正常遍历数据,遍历完后,就会退出遍历     
                intChan1 := make(chan int , 100)     
                // 写入数据     
                for i := 0; i < cap(intChan1); i++ {         
                        intChan1<- i * 2     
                }     
                close(intChan1)     
                // 取数据     
                for v := range intChan1 {         
                        fmt.Println("v=", v)     
                }
     
        管道阻塞的机制
            如果只是向管道写入数据,而没有读取,就会出现阻塞而deadlock,原因是intChan容量是10,而writeData会写入50个数据,因此会阻塞在writeData的 intChan<- i 
            如果,编译器(运行)发现一个管道只有写,而没有读,则该管道,会阻塞。
            写管道和读管道的频率不一致,无所谓。
        
     
    综合案例
        利用goroutine和channel 实现1-200000数字中,哪些是素数(prime)
        代码实现:
            package main 
            import (     "fmt" ) 
            func putNum(intChan chan int) {     
                    for i:=1 ; i<= 200000; i ++ {         
                            intChan<- i      
                    }      
                    close(intChan) 
            } 
            func checkPrime(intChan chan int, primeChan chan int, exitChan chan bool) {     
                    for {         
                            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("有一个primeCheck 管道完成任务, 退出")     
                // 这里不能关闭 primeChan 因为可能别的协程还在写数据     
                // 要写标识退出的 true到 exitChan里     
                exitChan<- true  
            } 
            func main() {     
                    // 协程求素数 1-200000数字中,哪些是素数?     
                    // 传统for循环方法实现没有问题,但是无法有效利用多核     
                    // 将统计素数的任务分配给多个(6)个协程     
                    intChan := make(chan int, 10000)     
                    primeChan := make(chan int, 100000)     
                    exitChan := make(chan bool, 6)     
                    go putNum(intChan)     
                    // 开启6个协程   统计素数并且放入 primeChan中  
                    for i := 0; i < 6; i ++ {         
                            go checkPrime(intChan, primeChan, exitChan)     
                    }     
                    // 主线程处理     
                    go func(){         
                            for i := 0; i < 6; i ++ {             
                                    // 不断地从 exitChan 中取值,只有这样主线程才不会马上退出             
                                    <-exitChan         
                            }         
                            // 取完说明 所有协程已经把数据全部放入到 primeChan中,然后关闭管道         
                            close(primeChan)     
                    }()          
                    // 遍历 primeChan 取出结果     
                    for {         
                            v, ok := <-primeChan         
                            if !ok {             
                                    break          
                            }         
                            fmt.Println("prime num ==", v)     
                    }     
                    fmt.Println("main 主线程退出~~~") 
                }
     
        传统方式:
            package main 
            import (     "fmt"     "time" ) 
            func main() {     
                    start := time.Now().Unix()     
                    for num := 1; num <= 200000; num ++ {         
                            flag := true  // 素数标识         
                            for i := 2; i < num; i ++ {             
                                    if num % i == 0 {                 
                                            flag = false                  
                                            break              
                                    }         
                            }         
                            if flag {              
     
                            }     
                    }     
                    end := time.Now().Unix()     
                    fmt.Println("传统方式耗时--", end - start) 
            }
            
        效率相比,goroutine + channel 比传统方法 快基本是cpu的个数的倍数;
            也可以打开任务管理器看一下开启多个协程时,cpu的使用率(go协程的话开和cpu相同的协程数的话跑的过程中,cpu使用率基本可以达到100%,而传统方式只会提高使用率但是不会达到100%
        
     
     
        
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
        
  • 相关阅读:
    elasticsearch 5.x 系列之七 基于索引别名的零停机升级服务
    Linux 查看系统硬件信息(实例详解)
    linux grep命令详解
    Boot loader: Grub进阶(转)
    Boot loader: Grub入门(转)
    内核模块管理(转)
    Centos启动流程(转)
    Linux 内核启动流程(转)
    程序的运行顺序(转)
    查询进程打开的文件(转)
  • 原文地址:https://www.cnblogs.com/guo-s/p/14217675.html
Copyright © 2011-2022 走看看