进阶-1

复习

  1. Go语言保留了C语言中的指针,但又有所不同;
    1. 默认值为 nil
    2. 操作符 & 取变量地址,* 通过指针访问目标对象;
    3. 不支持指针运算,不支持 -> 运算符,直接使用 . 访问目标成员。
  2. 32为系统:4G
    1. 3G - 4Gkernel占用
    2. 0 - 3G:用户使用,可分为代码区、只读数据区、数据区、未初始化数据区、堆、栈、其他
    3. :默认大小为1M,Windows系统上可扩展到8M,考虑到栈区内存很小,所以由操作系统管理栈的释放;
    4. :通常内存空间在 1G 以上,在程序合理的情况下,GC机制能有效控制释放;
  3. 切片并不是数组或数组指针,它是通过内部指针和相关属性引用数组片段,以实现变长放案;
    1. 切片并不是真正意义上的动态数组,而是一个引用类型,数组是值传递;
    2. 切片是一种数据结构体,总是指向一个底层array
    3. 切片创建的多种方式
    slice := []int{1, 2, 3}
    slice := make([]int, 长度, 容量)
    slice := make([]int, 长度)  // 省略容量时,容量==长度
    
  4. make()函数只能用于创建 切片、Map、Channel
  5. 相同结构体类型:成员变量的类型、个数、顺序都完全一致;相同结构体变量之间可以直接赋值;
    1. 结构体变量的地址 == 结构体首个元素的地址
    2. 结构体是值传递,内存消耗大,效率低下;
  6. unsave.Sizeof(xxx):查看占用的字节数;
    1. Go语言的布尔类型是用 0/1 模拟的,所以占用 1 个字节;
    2. 不管何种类型的指针,在64位操作系统下,占用字节数恒为8,所以在传递结构体时,通常使用 指针 实现地址传递。
  7. 函数不能返回局部变量的地址值(指针)!因为局部变量保存在栈中,栈区释放之后,局部变量不受系统保护,随时可能把内存分配给其他程序;
  8. 虚拟地址映射:我们常说的内存地址其实虚拟地址,因为操作系统并不是希望直接把物理内存地址暴露给用户;
    1. 堆区的地址是连续的,而真正的物理存储却并不是连续的内存区间,目的是为了让内存得到充分的利用;
    2. 磁盘的最小单位是扇区,内存的最小单位是
  9. 当一个进程启动时,系统会自动打开三个文件:标准输入、标准输出、标准错误 --- stdin、stdout、stderr ,进程结束,系统会自动关闭它们。

并发

  1. 并行与并发
    1. 并行:借助多核CPU实现,是真并行;
    2. 并发:宏观的用户体验上,程序是并行的;微观上,其实是轮换使用CPU时间轮片,飞快地切换,是假并行。
  2. 程序与进程
    1. 程序:编译成功得到的二进制文件,占用磁盘空间;
    2. 进程:运行起来地程序,占用系统资源;
  3. 进程状态
    1. 5种基本状态:初始态、就绪态、运行态、挂起/阻塞态、终止/停止态
    2. 初始态为进程准备阶段,常与就绪态结合考虑;
进程状态.png
  1. 进程并发
    1. 系统开销比较大,占用资源比较多,开启进程数量比较少;
    2. 父进程通过fork创建子进程,子进程再创建新的进程,且父进程永远无法预测子进程什么时候结束;
    3. 在 unix/linux 系统下,还会产生孤儿进程和僵尸进程;
    4. 孤儿进程:父进程先于子进程结束,则子进程成为孤儿进程,子进程地父进程成为init进程,称为init进程领养孤儿进程;
    5. 僵尸进程:进程终止,父进程尚未回收,子进程残留资源存放于内核种,变成僵尸进程;
    6. Windows系统地进程和Linux地进程有所不同,它从不执行任何东西,只是为线程提供执行环境,由线程负责执行包含在进程地址空间中的代码;在创建一个进程时,系统也会自动为其创建第一个线程,称为主线程。
  2. 线程并发
    1. 进程是最小的系统资源分配单位
    2. 线程:LWP 轻量级的进程,最小的执行单位,CPU分配时间轮片的对象
  3. 线程同步
    1. 线程同步机制:锁
    2. 互斥锁:建议锁,拿到锁以后才能访问数据,没有拿到锁的线程阻塞等待;
    3. 读写锁:一把具有读属性和写属性的锁,写独占,读共享,且写的优先级最高。
  4. 协程并发:轻量级线程,占用系统资源最少
    1. 协程最大的优势在于轻量级,可以轻松创建数万个而不导致系统资源衰竭,而线程和进程通常很难达到1万个;
    2. 一个线程可以有任意多个协程,但某一个时刻只能有一个协程在运行,多个协程共享该线程分配到的计算机资源;
    3. 在协程中,调用一个任务就像调用一个函数一样,消耗的系统资源最少,但能达到进程/线程并发的相同效果。

Goroutine

  1. Go语言为并发编程而内置的上层API基于顺序通信进程模型CSP,这就意味着显式锁是可以避免的,因为Go通过相对安全的通道发送和接收数据以实现同步,大大简化了并发程序的编写;
  2. Go语言中的并发程序主要通过两种手段实现:GoroutineChannel
  3. Goroutine是Go语言并行设计的核心,也成为Go程,其本质就是协程,十几个Goroutine在底层可能只有五六个线程,Go语言内部实现了Goroutine之间的内存共享;
  4. 执行Goroutine只需极少的栈内存(4-5KB,会根据相应的数据伸缩),可同时运行成千上万个并发任务;
  5. 创建:只需要在函数调用语句前添加 go 关键字,就可以创建并发执行单元,调度器会自动将其安排到合适的系统线程上执行;
    func sing()  {
        for i:=0; i<10; i++ {
            fmt.Println("sing: ", i)
            time.Sleep(10)
        }
    }
    func dance()  {
        for i:=0; i<10; i++ {
            fmt.Println("dance: ", i)
            time.Sleep(10)
        }
    }
    func main() {
        go sing()
        go dance()
        for  {
            ;
        }
    }
  1. 当一个程序启动时,主函数main()在一个单独的Goroutine中运行,又称为main Goroutine(主Go程),在主Go程中开启的Go程称为子Go程;
  2. 特性:主Go程一旦结束,进程也就结束了,子Go程也就随之退出了!

runtime包

  1. runtime.Gosched():用于让出CPU时间片,让出当前Goroutine的执行权限,调度器安排其他等待的任务执行,并在下次重新获得CPU时间轮片时,从之前出让CPU的位置继续向下执行!
    1. 虽然runtime.Gosched()time.Sleep()都能让出时间片,但它们的量级是不同的;
    2. runtime.Gosched()让时间片之后,会立即加入等待区,而time.Sleep()则必须等待休眠时间到了之后才会加入等待区。
  2. runtime.Goexit():立即终止当前 Goroutine(非主Go程)的执行,调度器确保所有已注册 defer 延迟调用被执行;
    1. return 只是针对一个函数的结束,其后面注册的 defer 也不会再执行,因为还没来得及注册;
    2. Goexit()针对的是整个当前的Go程,一旦执行,当前所在的Go程立即终止,其后的代码不在执行,包括尚未注册的defer
  3. GOMAXPROCS(n):设置可同时执行(并行)的CPU核数的最大值,并返回之前的值;
    n := runtime.GOMAXPROCS(1)  // 将CPU设置为单核
    
    1. 首次调用返回默认值,如果 n < 1 ,则设置失败,不会更改当前值;
    2. 默认会使用CPU的全核工作,但也会受电源等元器件的影响,为了保护系统服务正常工作,而选择降频工作,即不启用全核。
  4. NumCPU():查询本地机器的逻辑CPU个数;
  5. GC():手动执行一次垃圾回收;

Channel

  1. Channel是Go语言中的一个核心类型,可以看成管道,分为两个端:写端(传入端)、读端(传出端)
    1. 并发核心单元通过Channel就可以发送/接收数据进行通讯,在一定程度上进一步降低编程难度;
    2. Channel是一个数据类型,主要用于解决协程的同步问题以及协程之间的数据共享(数据传递)问题;
    3. Goroutine运行在相同的地址空间,因此访问共享内存必须做好同步,Goroutine奉行通过通信来共享内存,而不是共享内存来通信;
    4. 引用类型Channel可用于多个Goroutine的通讯,其内部实现了同步,确保并发安全。
  2. ChannelMap类似,由 make() 创建底层数据结构,且它是引用类型,默认值为nil,遵循地址传递;
    1. 声明
    var channel chan int   // 声明一个Channel通道,默认可读可写
    var read <-chan int   // 声明一个只读的Channel通道
    var write chan<- int   // 声明一个只写的Channel通道
    
    1. 创建
    channel := make(chan Type, capacity)  //创建一个Channel
    
    1. chan是声明Channel的关键字,Type 表示Channel收发数据的类型;
    2. capacity可省略,默认值为0,此时Channel是无缓冲阻塞读写的;当capacity > 0时,Channel是有缓冲非阻塞的,直到写满capacity个元素才阻塞写入。
  3. Channel通过操作符 <- 来收发数据
    channel <- value  // 写端,发送 value 到 channel,value 的数据类型与定义类型保持一致
    <-channel     // 接收并将其丢弃,读端
    x := <-channel   // 从 channel 中接收数据,并赋值给x,读端
    x, ok := <-channel  // ok 可以检查通道是否已关闭,或者是否为空,读端
    
    1. len()获取的是Channel中剩余未读取的数据个数;
    2. cap()获取Channel的容量。
  4. For example
    var channel = make(chan int)  // 创建 Channel 通道
    func sing()  {
        printer("hello")
        channel <- 8   // 执行完打印操作之后,再向Channel中发送数据
    }
    func dance()  {
        <- channel   // 阻塞、等待接收Channel中的数据
        printer("world")
    }
    func printer(s string) {  // 打印操作,每次睡眠 1s
        for _,ch:=range s {
            fmt.Printf("%c", ch)
            time.Sleep(1000*time.Millisecond)
        }
    }
    func main() {
        go sing()
        go dance()
        for  {
            ;
        }
    }
    // helloworld
    
  5. 无缓冲阻塞Channel:同步通信
    1. 无缓冲通道是指在接收前没有能力保存任何值的通道,通道容量为0
    2. 它要求写端和读端同时准备好,才能完成收发操作,否则先执行的一端Go程就会阻塞等待,即读写同步!比如打电话
        func main() {
            ch := make(chan string)
            go func() {
                for i:=0; i<5; i++ {
                    fmt.Println(i)
                    time.Sleep(1000*time.Millisecond)
                }
                <- ch  // 循环结束才会消费通道中的数据
            }()
            ch <- "hello"  // 写端-主Go程陷入阻塞,等待读端消费数据,然后才能重新加入CPU时间轮片
            fmt.Println("main end")
        }
        // 0 1 2 3 4    main end
    
    1. 这种对通道进行发送和接收的交互行为本身就是同步的,其中任意一个操作都无法离开另一个操作单独存在,否则就会造成死锁;
    2. 阻塞:由于某种原因导致数据没有到达,当前协程/线程持续处于等待状态,直到条件满足,才能解除阻塞;
    3. 同步:在两个或多个协程/线程间,保持数据内容的一致性。
  6. 有缓冲Channel:异步通信
    1. 缓冲区可以进行数据存储,达到容量上限之后才会阻塞,具备异步能力!比如发短信
        func main() {
            ch := make(chan int, 3)
            go func() {
                for i:=0; i<8; i++ {
                    fmt.Println("func: ", i)
                    ch <- i
                }
            }()
            for i := 0; i < 8; i++ {
                n := <-ch
                fmt.Println("main: ", n)
                time.Sleep(1*time.Second)  // 睡眠 1s
            }
            fmt.Println("main end")
        }
    
  7. close(ch):内置函数,关闭Channel
    1. 一端关闭了通道,另一端是可以判断通道是否已经关闭的;
        if n, ok := <-ch; ok {
    
        }
    
    1. 如果对端已经关闭了通道,okfalse,主要也是关闭发送端;
    2. 数据未发送完,不应该关闭通道;已关闭的通道不能再发送数据,否则报异常:send on closed channel
    3. 写端已关闭了通道,对于无缓冲的Channel,读端还可以从中读取到数据,结果为数据类型的默认值;对于有缓冲的Channel,如果缓冲区内还有数据,则先读数据,读完之后还可以继续读,结果也是数据类型的默认值;
  8. for-range 可以遍历通道,获取其中的数据
    for n := range channel {
    }
    

单向Channel

  1. 默认的Channel是双向的
  2. 单向写Channelvar send chan <- int
  3. 单向读Channelvar recv <-chan int
  4. 双向Channel可以隐式转换为任意一种同类型的单向Channel,反之则不行!
    ch := make(chan int)
    var send chan <- int = ch
    send <- 89
    
    1. Channel一定是成对出现的,只有单向Channel会造成死锁;
    2. 不能对单向写Channel进行读取操作。
  5. 单向Channel传参
    func send(s chan <- int)  {  // 单向写Channel
        s <- 89
        close(s)
    }
    func recv(r <- chan int)  {  // 单向读Channel
        n := <- r
        fmt.Println("recv: ", n)
    }
    func main() {
        ch := make(chan int)  // 双向Channel
        go func() {
            send(ch)
        }()
        recv(ch)  // 89
    }
    

生产者消费者模型

生产者 -> 缓冲区 -> 消费者

  1. 缓冲区的作用
    1. 解耦:降低生产者和消费者之间的耦合度;
    2. 并发:生产者和消费者的数量不对等时,保持正常通信;
    3. 缓存:生产者和消费者的数据处理速度不一致时,暂存数据;
  2. Channel可以作为缓冲区,实现生产者(Go程-1)与消费者(Go程-2)的通信

定时器

  1. time.Timer 是一个定时器,代表未来的一个单一事件,可以设置等待时间;
    type Timer struct {
        C <-chan Time
        r runtimeTimer
    }
    
    1. 它提供一个Channel,在定时时间到达之前,没有数据写入通道 C 就会一直阻塞;定时时间到了之后,系统会自动向 C 通道中写入当前时间,阻塞即刻被解除;
    2. time.NewTimer(d):创建定时器,传入定时时长,返回值为*Timer
    t := time.NewTimer(time.Second*2)  //定时2s
    now := <- t.C  //从 Timer 中读通道C 的数据,也就是当前时间
    
    1. 设置了定时器后,当前Go程会阻塞,等待定时时间到,返回Timer指针;
    2. 应从通道中读出数据,否则会一直阻塞;
    3. t.Stop():停止定时器;
    4. t.Reset(d):重置定时时长;
  2. timer.Sleep() 属于单纯的等待;
  3. timer.After() 也可以实现定时等待,返回一个Time类型的Channel
    now <- timer.After(time.Second*2)  // 从通道中取出当前时间
    
  4. 周期定时:timer.NewTicker(d),系统每隔 d 时间自动向通道中写入一次当前系统时间,返回*Ticker
    type Ticker struct {
        C <-chan Time
        r runtimeTimer
    }
    
    func main() {
        t := time.NewTicker(time.Second)  // 设置周期为 1s
        go func() {
            for {
                now := <-t.C
                fmt.Println(now)
            }
        }()
        for {
            ;
        }
    }
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,230评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,261评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,089评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,542评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,542评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,544评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,922评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,578评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,816评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,576评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,658评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,359评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,920评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,859评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,381评论 2 342

推荐阅读更多精彩内容