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

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

   fmt.Println("1")
   //单位是纳秒,表示阻塞多长时间
   //e9表示10的9次方
   time.Sleep(1e9)
   fmt.Println("2")
二.延迟执行
  • 延迟指定时间后执行一次,但是需要注意在触发时程序没有结束

  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 表达式
 代码示例
  • 对比多次调用函数和使用goroutine的效果

package main
​
import "fmt"
import "time"
​
func main() {
   //正常调用,输出3遍1 2 3 4 5(每个数字后换行)
   //for i:=1; i<=3; i++ {
   // go demo()
   //}
​
   /*
   添加go关键字后发现控制台什么也没有输出
   原因:把demo()设置到协程后没等到函数执行,主
   线程执行结束
    */
   for i := 1; i <= 3; i++ {
      go demo(i)
   }
}
​
func demo(index int) {
   for i := 1; i <= 5; i++ {
      fmt.Printf("第%d次执行,i的值为:%d\n", index, i)
   }
}
  • 添加休眠等待goroutine执行结束

  • 这种方式很大的问题就是休眠时间,如果休眠时间设置过小,可能goroutine并没有执行完成,如果休眠时间设置过大,影响程序执行执行.找到的本次执行的休眠时间,下次程序执行时这个休眠时间可能”过大”或”过小"

  • 通过程序运行结果发现每次执行结果都不一定是一样的,因为每个demo()都是并发执行

package main
​
import "fmt"
import "time"
​
func main() {
   //正常调用,输出3遍1 2 3 4 5(每个数字后换行)
   //for i:=1; i<=3; i++ {
   // go demo()
   //}
​
   /*
   添加go关键字后发现控制台什么也没有输出
   原因:把demo()设置到协程后没等到函数执行,主
   线程执行结束
    */
   for i := 1; i <= 3; i++ {
      go demo(i)
   }
​
   /*
   添加休眠,让主线程等待协程执行结束.
   具体休眠时间需要根据计算机性能去估计
   次数没有固定值
    */
   time.Sleep(3e9)
   fmt.Println("程序执行结束")
}
​
func demo(index int) {
   for i := 1; i <= 5; i++ {
      fmt.Printf("第%d次执行,i的值为:%d\n", index, i)
   }
}

[备注:

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

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

  • 在Golang中WaitGroup存在于sync包中,在sync包中类型都是不应该被拷贝的.源码定义如下

// A WaitGroup waits for a collection of goroutines to finish.
// The main goroutine calls Add to set the number of
// goroutines to wait for. Then each of the goroutines
// runs and calls Done when finished. At the same time,
// Wait can be used to block until all goroutines have finished.
//
// A WaitGroup must not be copied after first use.
type WaitGroup struct {
    noCopy noCopy
​
    // 64-bit value: high 32 bits are counter, low 32 bits are waiter count.
    // 64-bit atomic operations require 64-bit alignment, but 32-bit
    // compilers do not ensure it. So we allocate 12 bytes and then use
    // the aligned 8 bytes in them as state.
    state1 [12]byte
    sema   uint32
}
  • Go语言标准库中WaitGroup只有三个方法

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

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

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

  type WaitGroup
  func (wg *WaitGroup) Add(delta int)
  func (wg *WaitGroup) Done()
  func (wg *WaitGroup) Wait()
代码示例
  • 使用WaitGroup可以有效解决goroutine未执行完成主协程执行完成,导致程序结束,goroutine未执行问题

package main
​
import (
   "fmt"
   "sync"
)
​
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()
}

 

五.互斥锁
package main

import (
	"fmt"
	"sync"
)
var (
	num=100
	wg sync.WaitGroup
)
func demo(){
	for i:=0;i<10 ;i++  {
		num=num-1

	}
	wg.Done()
}

func main() {
	for i:=0;i<10 ;i++  {
		wg.Add(1)
	     go demo()
	}
	wg.Wait()
	fmt.Println(num)

}

//此时未使用锁,可能出现数据错误

package main

import (
	"fmt"
	"sync"
)
var (
	num=100
	wg sync.WaitGroup
	m sync.Mutex
)
func demo(){
	m.Lock()
	for i:=0;i<10 ;i++  {
		num=num-1

	}
	m.Unlock()
	wg.Done()
}
func main() {
	for i:=0;i<10 ;i++  {
		wg.Add(1)
	     go demo()
	}
	wg.Wait()
	fmt.Println(num)
}
package main
​
import (
   "fmt"
   "sync"
   "time"
   "math/rand"
)
​
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("所有票卖完")
}

 

六.RWMutex读写锁
  • RWMutex 源码如下

// There is a modified copy of this file in runtime/rwmutex.go.
// If you make any changes here, see if you should make them there.
​
// A RWMutex is a reader/writer mutual exclusion lock.
// The lock can be held by an arbitrary number of readers or a single writer.
// The zero value for a RWMutex is an unlocked mutex.
//
// A RWMutex must not be copied after first use.
//
// If a goroutine holds a RWMutex for reading and another goroutine might
// call Lock, no goroutine should expect to be able to acquire a read lock
// until the initial read lock is released. In particular, this prohibits
// recursive read locking. This is to ensure that the lock eventually becomes
// available; a blocked Lock call excludes new readers from acquiring the
// lock.
type RWMutex struct {
    w           Mutex  // held if there are pending writers
    writerSem   uint32 // semaphore for writers to wait for completing readers
    readerSem   uint32 // semaphore for readers to wait for completing writers
    readerCount int32  // number of pending readers
    readerWait  int32  // number of departing readers
}
  • Go语言标准库中API如下

  type RWMutex
  func (rw *RWMutex) Lock()//禁止其他协程读写
  func (rw *RWMutex) Unlock()
  func (rw *RWMutex) RLock()//禁止其他协程写入,只能读取
  func (rw *RWMutex) RUnlock()
  func (rw *RWMutex) RLocker() Locker
  • Go语言中的map不是线程安全的,多个goroutine同时操作会出现错误.

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

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

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

package main
​
import (
   "fmt"
   "sync"
   "strconv"
)
​
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("程序结束")
}