Go语言学习教程(十五)

一、线程休眠

* Go语言中main()函数为主线程(协程),程序是从上向下执行的

* 可以通过time包下的Sleep(n)让程序阻塞多少纳秒

   //单位是纳秒,表示阻塞多长时间

   //e9表示10的9次方

   time.Sleep(1e9)

二、延迟执行

* 延迟指定时间后执行一次,但是需要注意在触发时程序没有结束

    fmt.Println("开始")

   //2秒后执行匿名函数

   time.AfterFunc(2e9, func() {

      fmt.Println("延迟延迟触发")

   })

   time.Sleep(10e9)//一定要休眠,否则程序结束了

   fmt.Println("结束")

三、goroutine简介

* Golang中最迷人的一个优点就是从语言层面就支持并发

* 在Golang中的goroutine(协程)类似于其他语言的线程

* 并发和并行

    * 并行(parallelism)指不同的代码片段同时在不同的物理处理器上支持

    * 并发(concurrency)指同时管理多个事情,物理处理器上可能运行某个内容一半后就处理其他事情

    * 在一般看来并发的性能要好于并行.因为计算机的物理资源是固定的,较少的,而程序需要执行的内容是很多的.所以并发是”以较少的资源去去做更多事情”

* 几种主流并发模型

    * 多线程,每个线程只处理一个请求,只有请求结束后,对应的线程才会接收下一个请求.这种模式在高并发下,性能开销极大.

    * 基于回调的异步IO.在程序运行过程中可能产生大量回调导致维护成本加大,程序执行流程也不便于思维

    * 协程.不需要抢占式调用,可以有效提升线程任务的并发性,弥补了多线程模式的缺点;Golang在语言层面就支持,而其他语言很少支持

* goroutine的语法

    * 表达式可以是一条语句

    * 表达式也可以是函数,函数返回值即使有,也无效,当函数执行完成此goroutine自动结束

    go 表达式

四、WaitGroup简介

* Golang中sync包提供了基本同步基元,如互斥锁等.除了Once和WaitGroup类型, 大部分都只适用于低水平程序线程,高水平同步线程使用channel通信更好一些

* WaitGroup直译为等待组,其实就是计数器,只要计数器中有内容将一直阻塞

* 在Golang中WaitGroup存在于sync包中,在sync包中类型都是不应该被拷贝的

* Go语言标准库中WaitGroup只有三个方法

    * Add(delta int)表示向内部计数器添加增量(delta),其中参数delta可以是负数

    * Done()表示减少WaitGroup计数器的值,应当在程序最后执行.相当于Add(-1)

    * Wait()表示阻塞直到WaitGroup计数器为0

* 使用WaitGroup可以有效解决goroutine未执行完成而主协程执行完成,导致程序结束的goroutine未执行问题

    var wg sync.WaitGroup

    func main() {

       for i := 1; i <= 3; i++ {

          wg.Add(1)

          go demo(i)

       }

   //阻塞,知道WaitGroup队列中所有任务执行结束时自动解除阻塞

   fmt.Println("开始阻塞")

   wg.Wait()

   fmt.Println("任务执行结束,解除阻塞")

}

func demo(index int) {

   for i := 1; i <= 5; i++ {

      fmt.Printf("第%d次执行,i的值为:%d\n", index, i)

   }

   wg.Done()

}

五、互斥锁

* Go语言中多个协程操作一个变量时会出现冲突的问题

* go run -race 可以查看竞争

* 可以使用sync.Mutex对内容加锁

* 互斥锁的使用场景

    * 多个goroutine访问同一个函数(代码段)

    * 这个函数操作一个全局变量

    * 为了保证共享变量安全性,值合法性

* 使用互斥锁模拟售票窗口

var (

   //票数

   num = 100

   wg  sync.WaitGroup

   //互斥锁

   mu sync.Mutex

)

func sellTicker(i int) {

   defer wg.Done()

   for {

      //加锁,多个goroutine互斥

      mu.Lock()

      if num >= 1 {

         fmt.Println("第", i, "个窗口卖了", num)

         num = num - 1

      }

      //解锁

      mu.Unlock()

      if num <= 0 {

         break

      }

      //添加休眠,防止结果可能出现在一个goroutine中

      time.Sleep(time.Duration(rand.Int63n(1000) * 1e6))

   }

}

func main() {

   //设置随机数种子

   rand.Seed(time.Now().UnixNano())

   //计算器的起始值和票数相同

   wg.Add(4)

   go sellTicker(1)

   go sellTicker(2)

   go sellTicker(3)

   go sellTicker(4)

   wg.Wait()

   fmt.Println("所有票卖完")

}

六、读写锁

* Go语言中的map不是线程安全的,多个goroutine同时操作会出现错误.

* RWMutex可以添加多个读锁或一个写锁.读写锁不能同时存在.

    * map在并发下读写就需要结合读写锁完成

    * 互斥锁表示锁的代码同一时间只能有一个goroutine运行,而读写锁表示在锁范围内数据的读写操作

func main() {

   var rwm sync.RWMutex

   m := make(map[string]string)

   var wg sync.WaitGroup

   wg.Add(10)

   for i := 0; i < 10; i++ {

      go func(j int) {

         //没有锁在map时可能出现问题

         rwm.Lock()

         m["key"+strconv.Itoa(j)] = "value" + strconv.Itoa(j)

         fmt.Println(m)

         rwm.Unlock()

         wg.Done()

      }(i)

   }

   wg.Wait()

   fmt.Println("程序结束")

}

一、channel

* 线程通信在每个编程语言中都是重难点,在Golang中提供了语言级别的goroutine之间通信:channel

* channel不同的翻译资料叫法不一样.常见的几种叫法

    * 管道

    * 信道

    * 通道

* channel是进程内的通信方式,每个channel只能传递一个类型的值.这个类型需要在声明channel时指定

* channel在Golang中主要的两个作用

    * 同步

    * 通信

* Go语言中channel的关键字是chan

* 声明channel的语法

var 名称 chan 类型

var 名称 chan <- 类型 //只写

var 名称 <- chan 类型//只读

名称:=make(chan int) //无缓存channel

名称:=make(chan int,0)//无缓存channel

名称:=make(chan int,100)//有缓存channel

* 操作channel的语法:(假设定义一个channel名称为ch)

ch <- 值 //向ch中添加一个值

<- ch //从ch中取出一个值

a:=<-ch //从ch中取出一个值并赋值给a

a,b:=<-ch//从ch中取出一个值赋值给a,如果ch已经关闭或ch中没有值,b为false

* 简单无缓存通道代码示例

    * 此代码中如果没有从channel中取值c,d=<-ch语句,程序结束时go func并没有执行

    * 下面代码示例演示了同步操作,类似与WaitGroup功能,保证程序结束时goroutine已经执行完成

    * 向goroutine中添加内容的代码会阻塞goroutine执行,所以要把ch<-1放入到goroutine有效代码最后一行

    * 无论是向channel存数据还是取数据都会阻塞

    * close(channel)关闭channel,关闭后只读不可写

func main() {

   ch := make(chan int)

   go func() {

      fmt.Println("进入goroutine")

      // 添加一个内容后控制台输出:1 true

      //ch<-1

      //关闭ch控制台输出:0 false

      close(ch)

   }()

   c, d := <-ch

   fmt.Println(c, d)

   fmt.Println("程序执行结束")

}

* 使用channel实现goroutine之间通信

    * channel其实就是消息通信机制实现方案,在Golang中没有使用共享内存完成线程通信,而是使用channel实现goroutine之间通信

func main() {

   //用于goroutine之间传递数据

   ch := make(chan string)

   //用于控制程序执行

   ch2 := make(chan string)

   go func() {

      fmt.Println("执行第一个goroutine,等待第二个goroutine传递数据")

      content := <-ch

      fmt.Println("接收到的数据为:", content)

      ch2 <- "第一个"

   }()

   go func() {

      fmt.Println("进入到第二个,开始传递数据")

      ch <- "内容随意"

      close(ch)

      fmt.Println("发送数据完成")

      ch2 <- "第二个"

   }()

   result1 := <-ch2

   fmt.Println(result1, "执行完成")

   result2 := <-ch2

   fmt.Println(result2, "执行完成")

   fmt.Println("程序执行结束")

}

* 可以使用for range获取channel中内容

    * 不需要确定channel中数据个数

func main() {

   ch:=make(chan string)

   ch2:=make(chan int)

   go func() {

      for i:=97;i<97+26;i++{

         ch <- strconv.Itoa(i)

      }

      ch2<-1

   }()

   go func() {

      for c := range ch{

         fmt.Println("取出来的",c)

      }

   }()

   <-ch2

   fmt.Println("程序结束")

}

* channel是安全的.多个goroutine同时操作时,同一时间只能有一个goroutine存取数据

func main() {

   ch := make(chan int)

   for i := 1; i < 5; i++ {

      go func(j int) {

         fmt.Println(j, "开始")

         ch <- j

         fmt.Println(j, "结束")

      }(i)

   }

   for j := 1; j < 5; j++ {

      time.Sleep(2 * time.Second)

      <-ch

   }

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容