并发编程相关知识

102 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 3 天,点击查看活动详情

对于并发知识的一些相关总结,发现自己对于进程、线程、协程只能够说出他们的概念,对于他们特性和不同还不是很了解,面试的时候就惨遭拷打了,所以做笔记总结一下。

进程

进程是程序在操作系统的一次执行过程,是操作系统进行资源分配的调度的一个最小单位(程序执行的最小单元)。是程序运行的载体。

线程

线程是进程执行中的一个实体,是CPU调度和分派的基本单位(操作系统分配的最小单元),是比进程更小的能独立运行的基本单位。

进程和线程的比较

  • 进程是程序执行的基本单元,而线程是操作系统分配资源的最小单元。

  • 一个进程中包含一个或者多个线程,同一进程中的线程共享程序的内存空间并且可以并发执行。进程之间相互独立,进程中的线程对其他进程不可见。

  • 线程比进程的上下文切换要快得多。

协程:

相比线程更加轻量,拥有独立的栈空间和栈内存,调度由用户自己控制,相当于轻量级的线程。类似于用户级别的线程,调度也是由用户实现。

协程和线程的比较

  • 协程的栈空间比线程更小,线程默认是1M,而协程是1K,更加轻量级,因此在相同内存下可以开启更多的协程。
  • 协程的切换在用户态,而线程的切换在内核态,因此减少了上下文消耗,提高了效率。
  • 只有一个线程,不会出现锁竞争的情况造成阻塞,共享资源只需要判断状态就好了。但是不适用于需要大量计算的多线程,适用于被阻塞、需要大量并发的场景。

goroutine:

golang中的并发设计的核心,也叫协程。它不需要用户关心底层逻辑内存分配、垃圾回收等,只需要专注于业务开发。每个4~5KB的栈空间内存占用,和由于实现机制而大幅减少的创造、销毁开销是Go高并发的核心。

并发

多线程程序在一个核的cpu上运行,就是并发。CPU根据时间片切分,交替执行多个程序。但是这个切换的过程我们用户是感知不到的。
并发

并行

多线程程序在多个核的cpu上运行,就是并行。多核CPU在同时执行多个程序
并行

并发和并行的区别:

并发主要由切换时间片来实现"同时"运行,并行则是直接利用多核实现多线程的运行,go可以设置使用核数,以发挥多核计算机的能力。

  • go语言中最小资源单位为go程---->goroutine,go语言原生支持并发,就是靠goroutine实现的。
  • 只需要在函数前增加go关键字
  • 在go中通过通信来共享内存,而不是通过共享内存来通信
  • 通过创建计数器WaitGroup,来保证主进程不会提前退出
//创建一个计数器WaitGroup,
var wg sync.WaitGroup
func main() {
	for i := 0; i < 10; i++ {
		wg.Add(1) //每启动一个go routine计数器+1
		go hello(i)
	}
	fmt.Println("主函数输出")
	wg.Wait() //等待所有计数器为0时才退出
}
func hello(i int) {
	defer wg.Done() //goroutine运行结束后计数器-1
	fmt.Println("运行go程:", i)
} 

管道之间通信

  • 通常通过for range来遍历管道内的数据,并且以此来判断有没有关闭
  • 双向通道可以赋值给单向读、写通道,但是反过来不行
  • 通道写完后一定要关闭,同时要保证读写次数一致,
var wg1 sync.WaitGroup

func main() {
	//声明channel变量如果不用make初始化则为nil
	var ch1 chan int // 声明一个传递整型的通道
	fmt.Println(ch1) //此时为空,因为没有初始化
	//创建无缓冲管道
	//numChan := make(chan int)
	//创建有缓冲管道
	numChan := make(chan int, 10)
	wg1.Add(2)
	go read(numChan)
	go write(numChan)
	//input(ch1, numChan)
	wg1.Wait()
}
func write(ch chan int) {
	defer wg1.Done()
	for i := 0; i < 50; i++ {
		ch <- i
		fmt.Println("往管道中写入数据", i)
	}
	//数据发送完之后一定要关闭,不然会报错deadlock
	close(ch)
}
func read(ch chan int) {
	defer wg1.Done()
	//使用for range判断管道是否关闭,关闭了的话读取完后就终止循环
	for i := range ch {
		fmt.Println("读取到数据", i)
	}
}

//在参数声明单向读或写channel,可以从双向管道赋值
func input(out chan<- int, in <-chan int) {
	
}

channel情况总结

进程

进程是程序在操作系统的一次执行过程,是操作系统进行资源分配的调度的一个最小单位(程序执行的最小单元)。是程序运行的载体。

线程

线程是进程执行中的一个实体,是CPU调度和分派的基本单位(操作系统分配的最小单元),是比进程更小的能独立运行的基本单位。

进程和线程的比较

  • 进程是程序执行的基本单元,而线程是操作系统分配资源的最小单元。

  • 一个进程中包含一个或者多个线程,同一进程中的线程共享程序的内存空间并且可以并发执行。进程之间相互独立,进程中的线程对其他进程不可见。

  • 线程比进程的上下文切换要快得多。

协程:

相比线程更加轻量,拥有独立的栈空间和栈内存,调度由用户自己控制,相当于轻量级的线程。类似于用户级别的线程,调度也是由用户实现。

协程和线程的比较

  • 协程的栈空间比线程更小,线程默认是1M,而协程是1K,更加轻量级,因此在相同内存下可以开启更多的协程。
  • 协程的切换在用户态,而线程的切换在内核态,因此减少了上下文消耗,提高了效率。
  • 只有一个线程,不会出现锁竞争的情况造成阻塞,共享资源只需要判断状态就好了。但是不适用于需要大量计算的多线程,适用于被阻塞、需要大量并发的场景。

goroutine:

golang中的并发设计的核心,也叫协程。它不需要用户关心底层逻辑内存分配、垃圾回收等,只需要专注于业务开发。每个4~5KB的栈空间内存占用,和由于实现机制而大幅减少的创造、销毁开销是Go高并发的核心。

并发

多线程程序在一个核的cpu上运行,就是并发。CPU根据时间片切分,交替执行多个程序。但是这个切换的过程我们用户是感知不到的。
并发

并行

多线程程序在多个核的cpu上运行,就是并行。多核CPU在同时执行多个程序
并行

并发和并行的区别:

并发主要由切换时间片来实现"同时"运行,并行则是直接利用多核实现多线程的运行,go可以设置使用核数,以发挥多核计算机的能力。

  • go语言中最小资源单位为go程---->goroutine,go语言原生支持并发,就是靠goroutine实现的。
  • 只需要在函数前增加go关键字
  • 在go中通过通信来共享内存,而不是通过共享内存来通信
  • 通过创建计数器WaitGroup,来保证主程不会提前退出
//创建一个计数器WaitGroup,
var wg sync.WaitGroup
func main() {
	for i := 0; i < 10; i++ {
		wg.Add(1) //每启动一个go routine计数器+1
		go hello(i)
	}
	fmt.Println("主函数输出")
	wg.Wait() //等待所有计数器为0时才退出
}
func hello(i int) {
	defer wg.Done() //goroutine运行结束后计数器-1
	fmt.Println("运行go程:", i)
} 

管道之间通信

  • 通常通过for range来遍历管道内的数据,并且以此来判断有没有关闭
  • 双向通道可以赋值给单向读、写通道,但是反过来不行
  • 通道写完后一定要关闭,同时要保证读写次数一致,
var wg1 sync.WaitGroup

func main() {
	//声明channel变量如果不用make初始化则为nil
	var ch1 chan int // 声明一个传递整型的通道
	fmt.Println(ch1) //此时为空,因为没有初始化
	//创建无缓冲管道
	//numChan := make(chan int)
	//创建有缓冲管道
	numChan := make(chan int, 10)
	wg1.Add(2)
	go read(numChan)
	go write(numChan)
	//input(ch1, numChan)
	wg1.Wait()
}
func write(ch chan int) {
	defer wg1.Done()
	for i := 0; i < 50; i++ {
		ch <- i
		fmt.Println("往管道中写入数据", i)
	}
	//数据发送完之后一定要关闭,不然会报错deadlock
	close(ch)
}
func read(ch chan int) {
	defer wg1.Done()
	//使用for range判断管道是否关闭,关闭了的话读取完后就终止循环
	for i := range ch {
		fmt.Println("读取到数据", i)
	}
}

//在参数声明单向读或写channel,可以从双向管道赋值
func input(out chan<- int, in <-chan int) {
	
}

channel情况总结