进程和线程
进程(Process)就是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基 本单位,进程是一个动态概念,是程序在执行过程中分配和管理资源的基本单位,每一个进 程都有一个自己的地址空间。一个进程至少有 5 种基本状态,它们是:初始态,执行态, 等待状态,就绪状态,终止状态,通俗的讲进程就是一个正在执行的程序。
线程 是进程的一个执行实例,是程序执行的最小单元,它是比进程更小的能独立运行的基 本单位
一个进程可以创建多个线程,同一个进程中的多个线程可以并发执行,一个程序要运行的话至少有一个进程
并发和并行
并发:多个线程同时竞争一个位置,竞争到的才可以执行,每一个时间段只有一个线程在执
行。
并行:多个线程可以同时执行,每一个时间段,可以有多个线程同时执行。
通俗的讲多线程程序在单核 CPU 上面运行就是并发,多线程程序在多核 CUP 上运行就是并行,如果线程数大于 CPU 核数,则多线程程序在多个 CPU 上面运行既有并行又有并发
协程goroutine
golang 中的主线程:(可以理解为线程/也可以理解为进程),在一个 Golang 程序的主线程 上可以起多个协程。Golang 中多协程可以实现并行或者并发
协程:可以理解为用户级线程,这是对内核透明的,也就是系统并不知道有协程的存在,是 完全由用户自己的程序进行调度的。Golang 的一大特色就是从语言层面原生支持协程,在 函数或者方法前面加 go 关键字就可创建一个协程。可以说 Golang 中的协程就是 goroutine
多协程和多线程:Golang 中每个 goroutine (协程) 默认占用内存远比 Java 、C 的线程少
OS 线程(操作系统线程)一般都有固定的栈内存(通常为 2MB 左右)
一个 goroutine (协程) 占用内存非常小,只有 2KB 左右,多协程 goroutine 切换调度开销方面远比线程要少
使用协程
func test() {
for i := 0; i
sync.WaitGroup 等待协程
import (
"fmt"
"sync"
)
// 需要导入sync包
// 声明WaitGroup
var wg sync.WaitGroup
func test() {
for i := 0; i
并行运行时使用的CPU核数
Go 运行时的调度器使用 GOMAXPROCS 参数来确定需要使用多少个 OS 线程来同时执行 Go 代码。默认值是机器上的 CPU 核心数。例如在一个 8 核心的机器上,调度器会把 Go 代码同 时调度到 8 个 OS 线程上。
Go 语言中可以通过 runtime.GOMAXPROCS()函数设置当前程序并发时占用的 CPU 逻辑核心数。
Go1.5 版本之前,默认使用的是单核心执行。Go1.5 版本之后,默认使用全部的 CPU 逻辑核 心数
import (
"fmt"
"runtime"
)
func main() {
// 获取当下计数器上面的CPU个数
cpuNUm := runtime.NumCPU()
fmt.Println(cpuNUm)
// ky自己设置使用多少CPU
runtime.GOMAXPROCS(cpuNUm - 1)
}
channel管道
管道是 Golang 在语言级别上提供的 goroutine 间的通讯方式,我们可以使用 channel 在
多个 goroutine 之间传递消息。如果说 goroutine 是 Go 程序并发的执行体,channel 就是它们
之间的连接。channel 是可以让一个 goroutine 发送特定值到另一个 goroutine 的通信机制。
Golang 的并发模型是 CSP(Communicating Sequential Processes),提倡通过通信共享内 存而不是通过共享内存而实现通信。
Go 语言中的管道(channel)是一种特殊的类型。管道像一个传送带或者队列,总是遵循先入先出的规则,保证收发数据的顺序。每一个管道都是一个具体类 型的导管,也就是声明 channel 的时候需要为其指定元素类型
管道被设计为一种并发安全的数据结构,可以在多个协程之间安全地进行通信
channel类型
channel是一种类型,一种引用类型
/*
声明管道类型的语法:
var 管道名 chan 管道传递的数据类型
*/
var int1 chan int // 传递int类型的管道
var str chan string
var b chan bool
var s chan []int
channel操作
管道服务器托管网有发送(send)、接收(receive)和关闭(close)三种操作。
发送和接收都使用
创建channel
向管道中存储数据,管道就必须有容量,所以需要使用make初始化分配容量
// 创建一个管道,传递int类型,容量为3
var ch = make(chan i服务器托管网nt, 3)
向管道存储数据
// 管道名
从管道获取数据
// 接收变量 :=
管道先入先出
var ch = make(chan int, 3)
// 向管道存储多条数据
ch
管道的值、长度和容量
var ch = make(chan int, 3)
// 管道的值是一个地址,长度是0是因为现在还没有写入值
// 值-0xc0000b2000-长度-0,容量-310
fmt.Printf("值-%v-长度-%v,容量-%v", ch, len(ch), cap(ch))
管道阻塞
-
无缓冲管道:如果创建管道的时候没有指定容量,那么我们可以叫这个管道为无缓冲的管道,无缓冲的管道又称为阻塞的管道
-
有缓冲管道:我们在使用 make 函数初始化 管道的时候为其指定管道的容量,有容量的管道就是有缓冲管道
-
只要管道的容量大于零,那么该管道就是有缓冲的管道
- 容量为1有一条数据的管道,再存储,阻塞管道
- 容量为1有一条数据的管道,取一条数据后,再次取值,阻塞管道
管道遍历和关闭管道
当向管道中发送完数据时,我们可以通过 close 函数来关闭管道。
当管道被关闭时,再往该管道发送值会引发 panic,从该管道取值的操作会先取完管道中的值,再然后取到的值一直都是对应类型的零值
func main() {
var ch = make(chan int, 10)
for i := 0; i
协程Goroutine 结合 Channel 管道 同步操作
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
// 定义一个向管道写入数据的方法
func WriteData(ch chan int) {
for i := 0; i
/*
多个协程打印素数
1.协程存放数字
2.多个协程计算是否素数
3.协程打印素数
*/
var wg = sync.WaitGroup{}
// 向intChan放入数字
func PutNum(intChan chan int) {
for i := 2; i
单向管道
有的时候我们会将管道作为参数在多个任务函数间传递,很多时候我们在不同的任务函数中 使用管道都会对其进行限制,比如限制管道在函数中只能发送或只能接收,默认情况下,管道是双向管道,可读可写
// 双向管道 可读可写
var ch = make(chan int, 10)
ch
// 写入数据方法,类型为只写管道
func WriteData(ch chan
select多路复用
遍历管道时,如果不关闭会阻塞而导致 deadlock,在实际开发中,可能我们不好确定什么关闭该管道
select 的使用类似于 switch 语句,它有一系列 case 分支和一个默认的分支。每个 case 会对应一个管道的通信(接收或发送)过程。select 会一直等待,直到某个 case 的通信操作完成 时,就会执行 case 分支对应的语句
-
可处理一个或多个 channel 的发送/接收操作。
-
如果多个 case 同时满足,select 会随机选择一个。
-
对于没有 case 的 select{}会一直等待,可用于阻塞 main 函数
func main() {
// 管道一 10个数字
intChan := make(chan int, 10)
for i := 0; i
并发安全和锁
互斥锁
互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个 goroutine 可以访问共享资源。Go 语言中使用 sync 包的 Mutex 类型来实现互斥锁
使用互斥锁能够保证同一时间有且只有一个 goroutine 进入临界区,其他的 goroutine 则在等 待锁;当互斥锁释放后,等待的 goroutine 才可以获取锁进入临界区,多个 goroutine 同时等 待一个锁时,唤醒的策略是随机的
- Lock:锁定共享资源
- Unlock:解锁共享资源
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
// 声明一个互斥锁
var mutex sync.Mutex
var count int = 0
func test() {
// 多个协程同时访问时,先获取锁,然后执行代码,最后解锁,同时只有一个协程能获取到锁,获取不到锁的协程就等待
// 加锁
mutex.Lock()
count++
fmt.Println(count)
// 解锁
mutex.Unlock()
}
func main() {
for i := 0; i
读写互斥锁
互斥锁是完全互斥的,但是有很多实际的场景下是读多写少的,当我们并发的去读取一个资源不涉及资源修改的时候是没有必要加锁的,这种场景下使用读写锁是更好的一种选择。
读写锁在 Go 语言中使用 sync 包中的 RWMutex 类型。
读写锁分为两种:读锁和写锁。当一个 goroutine 获取读锁之后,其他的 goroutine 如果是获取读锁会继续获得锁,如果是获取写锁就会等待;当一个 goroutine 获取写锁之后,其他的goroutine 无论是获取读锁还是写锁都会等待
var wg sync.WaitGroup
// 声明读写锁
var mutex sync.RWMutex
// 写的方法 互斥的
func WriteData() {
mutex.Lock() // +写的互斥锁
fmt.Println("执行写操作")
mutex.Unlock() // 解写的互斥锁
wg.Done()
}
// 读的方法 并行的
func ReadData() {
mutex.RLock() // +读的互斥锁
fmt.Println("执行读操作")
mutex.RUnlock() // 解读的互斥锁
wg.Done()
}
func main() {
// 开启10个协程执行写操作
for i := 0; i
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net