首页 > 编程语言 >狂神说Go语言—并发编程

狂神说Go语言—并发编程

时间:2023-01-01 22:45:21浏览次数:39  
标签:fmt 编程 Println 线程 go time Go 狂神 main

聊聊进程、线程、协程

多线程

image-20230101153415784

上方左图所示:在主线程中为main方法左图的右边为test方法,在main方法中调用test方法,mian 方法执行就会先去执行test方法,执行完后再回到main方法往下执行

右图:因为多条执行路径,就会同时执行,既执行了main方法,又执行了test方法

程序、进程、线程

image-20230101154004351

image-20230101154346812

并发性和并行性

image-20230101154506460

说明:并行性Parallelism不会总是导致更快的执行时间。这是因为并行运行的组件可能需要相互通信,这种通信开销很高,因此,并行程序并不总是导致更快的执行时间

image-20230101154835165

进程、线程、协程

进程(Process),线程(Thread),协程(Coroutine,也叫轻量级线程)

进程

进程是一个程序在数据集中的一次动态执行过程,可以简单理解为"正在执行的程序",它是CPU资源分配和调度的独立单位

进程一般由程序、数据集、进程控制块三部分组成。我们编写的程序用来描述进程要完成哪些功能以及如何完成;
数据集则是程序在执行过程中所需要使用的资源;进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。进程的局限是创建、撤销和切换的开销比较大。

线程

线程是在进程之后发展出来的概念。线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序计数器、寄存器集合和堆栈共同组成。一个进程可以包含多个线程。
线程的优点是减小了程序并发执行时的开销,提高了操作系统的并发性能,缺点是线程没有白己的系统资源,同一进程的各线程可以共享进程所拥有的系统资源,如果把进程比作一个车间,那么线程就好比是车间里面的工人。不过对于某些独占性资源存在锁机制,处理不当可能会产生"死锁"。

协程

协程是一种用户态的轻量级线程,又称微线程,英文名Coroutine,协程的调度完全由用户控制。人们通常将协程和子程序(函数)比较着理解。

子程序调用总是一个入口,一次返回,一旦退出即完成了子程序的执行。
与传统的系统级线程和进程相比,协程的最大优势在于其"轻量级”,可以轻松创建上百万个而不会导致系统资源衰竭,而线程和进程通常最多也不能超过1万的。这也是协程也叫轻量级线程的原因。

Go语言对于并发的实现是靠协程,Goroutine

Goroutine

package main

import "fmt"

func main() {
	go hello() //启动协程
	for i := 0; i < 100; i++ {
		fmt.Println("main -", i)
	}
}

func hello() {
	for i := 0; i < 1-00; i++ {
		fmt.Println("hello -", i)
	}
}

我们需要了解Goroutine的规则
1、当新的Goroutine开始时,Goroutine调用立即返回。与函数不同,go不等待Goroutine执行结束
2、当Goroutine调用,并且Goroutine的任何返回值被忽略之后,go立即执行到下一行代码
3、main的Goroutine应该为其他的Goroutines执行。如果main的Goroutine终止了,程序将被终止,而其他Goroutine将不会运行

主Goroutine

封装main函数的goroutine称为主goroutine

主goroutine所做的事情并不是执行main函数那么简单。它首先要做的是:设定每一个goroutine所能申请的栈空间的最大尺寸。在32位的计算机系统中此最大尺寸为250MB,而在64位的计算机系统中此尺寸为1GB。如果有某个goroutine的栈空间尺寸大于这个限制,那么运行时系统就会引发一个栈溢出(stack overflow)的运行时恐慌。随后,这个go程序的运行也会终止。
此后,主goroutine会进行一系列的初始化工作,涉及的工作内容大致如下:
1、创建一个特殊的defer语句,用于在主goroutine退出时做必要的善后处理。因为主goroutine也可能非正常的结束2、启动专用于在后台清扫内存垃圾的goroutine,并设置GC可用的标识
3、执行main包中所引用包下的init函数
4、执行main函数
执行完main函数后,它还会检查主goroutine是否引发了运行时恐慌,并进行必要的处理。程序运行完毕后,主goroutine会结束自己以及当前进程的运行。

runtime包

获取系统信息

Gosched调度让出时间片,让别的goroutine先执行

package main

import (
   "fmt"
   "runtime"
)

func main() {
   fmt.Println("获取goroot目录", runtime.GOROOT())
   fmt.Println("获取操作系统", runtime.GOOS)
   fmt.Println("获得cpu数量", runtime.NumCPU())

   //匿名函数
   func() {
      for i := 0; i < 100; i++ {
         fmt.Println("goroutine")
      }
   }()

   for i := 0; i < 100; i++ {
      //让出时间片,让别的goroutime先执行,不一定可以让成功
      runtime.Gosched()
      fmt.Println("main")
   }
}

Goexit//终止当前的goroutine

package main

import (
   "fmt"
   "runtime"
   "time"
)

func main() {
   go func() {
      fmt.Println("strand")
      test()
      fmt.Println("end")
   }()
   time.Sleep(time.Second * 2) //休眠时间2秒
}
func test() {
   defer fmt.Println("defer -->test")
   //return会截止当前函数,不会终止后面的运行
   //return
   //Goexit,终止协程函数 协程函数内所有程序都会终止
   runtime.Goexit()
   fmt.Println("test")
}

临界资源安全问题

临界资源:指并发环境中多个进程、线程、协程共享的资源

什么是临界资源安全问题

在并发编程中对临界资源的处理不当,往往会导致数据不一致问题

package main

import (
   "fmt"
   "time"
)

func main() {
   a := 1
   go func() {
      a = 2
      fmt.Println("go->a", a)
   }()

   a = 3
   fmt.Println("main a1", a)
   time.Sleep(time.Second * 2)
   fmt.Println("main a2", a)
}

售票问题

并发本身并不复杂,但是因为有了资源竞争的问题,就使得我们开发出好的并发程序变得复杂起来,因为会引起很多莫名其妙的问题.
如果多个goroutine在访问同一个数据资源的时候,其中一个线程修改了数据,那么这个数值就被修改了,对于其他的goroutine来讲,这个数值可能是不对的。
举个例子,我们通过并发来实现火车站售票这个程序。共有10张票,4个售票口同时出售。

package main

import (
   "fmt"
   "time"
)

// 全局变量    票数为10
var ticket int = 10

func main() {
   go salesticket("售票口1")
   go salesticket("售票口2")
   go salesticket("售票口3")
   go salesticket("售票口4")
   time.Sleep(time.Second * 5)

}
func salesticket(name string) {
   for {
      if ticket > 0 {
         time.Sleep(time.Millisecond * 500)
         fmt.Println(name, "剩余票数:", ticket)
         ticket--
      } else {
         fmt.Println("票已经售完")
         break
      }
   }
}

互斥锁

要解决临界资源安全的问题,很多编程语言的解决方案都是同步。通过上锁的方式,某一时间段,只能允许一个goroutine来访问这个共享数据,当goroutine访问完毕,解锁后,其他的goroutine才能来访问

我们可以借助于sync包下 的锁操作

但是实际上,在Go的开发编程中有一句话很经典的话:不要以共享内存的方式去通信,而要以通信的方式去共享内存

在Go语言中并不鼓励用锁保护共享状态的方式,在不同的Goroutine中分享信息(以共享内存的方式去通信)。而是鼓励通过channel将共享状态或共享状态的变化在各个Goroutine之间传递(以通信的方式去共享内存),这样同样能像用锁一样保证在同一的时间只有一个Goroutine访问共享状态。
当然,在主流的编程语言中为了保证多线程之间共享数据安全性和一致性,都会提供一套基本的同步工具集,如锁,条件变量,原子操作等等,Go语言标准库也毫不意外的提供了这些同步机制,使用方式也和其他语言也差不多。

package main

import (
   "fmt"
   "sync"
   "time"
)

// 创建锁
var mutex sync.Mutex

// 全局变量    票数为10
var ticket int = 10

func main() {
   go salesticket("售票口1")
   go salesticket("售票口2")
   go salesticket("售票口3")
   go salesticket("售票口4")
   time.Sleep(time.Second * 10)

}
func salesticket(name string) {
   for {
      //检查之前先上锁
      mutex.Lock()
      if ticket > 0 {
         time.Sleep(time.Millisecond * 500)
         fmt.Println(name, "剩余票数:", ticket)
         ticket--
      } else {
         mutex.Lock()
         fmt.Println(name, "票已经售完")
         break
      }
      //操作完毕后再解锁
      mutex.Unlock()
   }
}

Waitgroup

waitgroup 等待组

//waitGroup
//Add()设置等待组中要执行的字Goroutine数量
//wait()让主Goroutine等待
//Done()让waitGroup中等待waitGroup-1
package main

import (
   "fmt"
   "sync"
   "time"
)

// waitGroup
// Add()设置等待组中要执行的字Goroutine数量
// wait()让主Goroutine等待
// Done()让waitGroup中等待waitGroup-1
var wg sync.WaitGroup

func main() {
   wg.Add(2)

   go test1()
   go test2()
   fmt.Println("main--ing")
   wg.Wait()
   fmt.Println("waitGroup解除")
}
func test1() {
   for i := 0; i < 10; i++ {
      time.Sleep(time.Millisecond * 500)
      fmt.Println("test1", i)
   }
   wg.Done()
}

func test2() {
   for i := 0; i < 10; i++ {
      time.Sleep(time.Millisecond * 500)
      fmt.Println("test2---", i)
   }
   wg.Done()
}
package main

import (
   "fmt"
   "sync"
   "time"
)

// 创建锁
var mutex sync.Mutex

// 全局变量    票数为10
var ticket int = 10

// 等待组
var wg2 sync.WaitGroup

func main() {
   wg2.Add(4)
   go salesticket("售票口1")
   go salesticket("售票口2")
   go salesticket("售票口3")
   go salesticket("售票口4")

   wg2.Wait()

   //time.Sleep(time.Second * 10)

}
func salesticket(name string) {
   defer wg2.Done()
   for {
      //检查之前先上锁
      mutex.Lock()
      if ticket > 0 {
         time.Sleep(time.Millisecond * 500)
         fmt.Println(name, "剩余票数:", ticket)
         ticket--
      } else {
         mutex.Unlock()
         fmt.Println(name, "票已经售完")
         break
      }
      //操作完毕后再解锁
      mutex.Unlock()
   }
}

标签:fmt,编程,Println,线程,go,time,Go,狂神,main
From: https://www.cnblogs.com/DuPengBG/p/17019175.html

相关文章

  • Django之csrf校验 CBV加装饰器以及auth认证模块
    目录Django之csrf校验CBV加装饰器以及auth认证模块一、csrf跨站请求伪造二、csrf校验策略(在提交数据的位置添加唯一标识)三、CBV加装饰器四、auth认证模块五、auth认证......
  • unix网络编程3.2——UDP(二)UDP可靠性传输1——KCP协议(上)
    目录系列文章unix网络编程1.1——TCP协议详解(一)unix网络编程2.1——高并发服务器(一)基础——io与文件描述符、socket编程与单进程服务端客户端实现unix网络编程2.2——高并......
  • unix网络编程3.3——UDP(三)UDP可靠性传输2——KCP协议(下)
    目录系列文章unix网络编程1.1——TCP协议详解(一)unix网络编程2.1——高并发服务器(一)基础——io与文件描述符、socket编程与单进程服务端客户端实现unix网络编程2.2——高并......
  • Flink:状态编程
    Flink中的状态在流处理中,数据是连续不断到来的。每个任务进行计算处理时,可以基于当前数据直接转换得到输出结果,也可以依赖一些其他数据。这些由一个任务维护,并且用来计算......
  • Django视频教程 - 基于Python的Web框架(全13集)
    Django是由Python驱动的开源模型-视图-控制器(MVC)风格的Web应用程序框架,使用Django可以在即可分钟内快速开发一个高品质易维护数据库驱动的应用程序。下面是一大坨关于Django......
  • php面向对象(OOP)编程
    大多数类都有一种称为构造函数的特殊方法。当创建一个对象时,它将自动调用构造函数,也就是使用new这个关键字来实例化对象的时候自动调用构造方法。构造函数的声明与其它操......
  • Django中logging的设置
    1.日志基础知识日志与我们的软件程序密不可分。它记录了程序的运行情况,可以给我们调试程序和故障排查提供非常有用的信息。每一条日志信息记录了一个事件的发生。具体而言......
  • osv-scanner google 开源漏洞扫描工具
    osv-scanner是google基于golang编写的开源漏洞扫描工具,支持基于osv数据库的扫描,生态比较好支持的扫描模式lockfilessbomsgit项目说明osv-scanner支持的扫描......
  • Django组件之form组件
    目录Django组件之form组件一、form组件二、forms组件渲染标签三、forms组件展示信息四、forms组件校验补充五、forms组件参数补充六、modelform组件Django组件之form组件......
  • 问题:django中对datetime类型数据在pycharm中sqlite3进行修改时,修改后datetime日期数据
    这是正在修改的  提交完之后  问题原因是sqlite数据库对日期类型不敏感,Pycharm直接插入会变成图中这样的时间戳,用POST请求添加数据或Django自带的后台管理插入不......