13、go并发编程

news2024/12/23 9:53:40

目录

  • 一、并发模型
  • 二、MPG并发模型
  • 三、Goroutine的使用
    • 1 - 协程使用
    • 2 - panic与defer
  • 四、channel的同步与异步‘’
    • 1 - 同步与异步channel
    • 2 - 关闭channel
  • 五、并发安全性
    • 1 - 资源竞争
    • 2 - 原子操作
    • 3 - 读写锁
    • 4 - 容器的并发安全
  • 六、多路复用
    • 1 - 阻塞I/O
    • 2 - 非阻塞I/O
    • 3 - 多路复用I/O

一、并发模型

  • 进程与线程:无论开辟多少个线程,并发是由内核数量决定的
    • 任何语言的并行,到操作系统层面,都是内核线程的并行
    • 同一个进程内的多个线程共享系统资源,进程的创建、销毁、切换比线程大很多
    • 从进程到线程再到协程, 其实是一个不断共享, 不断减少切换成本的过程
  • python:内核线程与进程一一对应
    在这里插入图片描述
  • C++、Java:内核线程与线程一一对应
    在这里插入图片描述
  • golang:动态变换,没有对应关系
    在这里插入图片描述
  • 协程与线程
协程线程
创建数量轻松创建上百万个协程而不会导致系统资源衰竭通常最多不能超过1万个
内存占用初始分配4k堆栈,随着程序的执行自动增长删除创建线程时必须指定堆栈且是固定的,通常以M为单位
切换成本协程切换只需保存三个寄存器,耗时约200纳秒线程切换需要保存几十个寄存器,耗时约1000纳秒
调度方式非抢占式,由Go runtime主动交出控制权(对于开发者而言是抢占式)在时间片用完后,由 CPU 中断任务强行将其调度走,这时必须保存很多信息
创建销毁goroutine因为是由Go runtime负责管理的,创建和销毁的消耗非常小,是用户级的创建和销毁开销巨大,因为要和操作系统打交道,是内核级的,通常解决的办法就是线程池
  • 查看逻辑核心数
func main() {
	fmt.Println(runtime.NumCPU()) //16
}

二、MPG并发模型

  • MPG概念
    • M(Machine)对应一个内核线程
    • P(Processor)虚拟处理器,代表M所需的上下文环境,是处理用户级代码逻辑的处理器
      • P的数量由环境变量中的GOMAXPROCS决定,默认情况下就是核数
    • G(Goroutine)本质上是轻量级的线程,G0正在执行,其他G在等待
      在这里插入图片描述
  • MPG模型
    • M和内核线程的对应关系是确定的
    • G0阻塞(如系统调用)时,P与G0、M0解绑,P被挂到其他M上,然后继续执行G队列
    • G0解除阻塞后,如果有空闲的P,就绑定M0并执行G0;否则G0进入全局可运行队列(runqueue),P会周期性扫描全局runqueue,使上面的G得到执行;如果全局runqueue为空,就从其他P的等待队列里偷一半G过来
      在这里插入图片描述

三、Goroutine的使用

1 - 协程使用

  • 两种启动协程的常见方式
    • 以下2份代码都无法打印出Add,因为main函数已经结束了
    • 如果想要打印出Add,可以添加time.Sleep来让协程运行完,才结束主进程
func Add(a, b int) int {
	fmt.Println("Add")
	return a + b
}

func main() {
	go Add(2, 4)
}
func main() {
	go func(a, b int) int {
		fmt.Println("Add")
		return a + b
	}(2, 4)
}
  • 优雅地等子协程结束:虽然上述情况我们使用time.Sleep来让协程运行完,才结束主进程,但是实际情况中我们并不知道协程需要多久才能执行完,此时就可以使用sync.WaitGroup来优雅的等待子协程结束
var wg = sync.WaitGroup{}

func Add() {
	defer wg.Done() //减1
	time.Sleep(100 * time.Millisecond)
	fmt.Println("Add")
}

func main() {
	wg.Add(2) //加2

	go Add()
	go Add()

	wg.Wait() //等待为0
}
  • 协程之间互不影响:除了main协程退出后,所有协程都会退出;其他的协程之间互不影响
var wg = sync.WaitGroup{}

func Add() {
	defer wg.Done() //减1
	go Sub()
	fmt.Println("over Add")
}

func Sub() {
	time.Sleep(2000 * time.Millisecond)
	fmt.Println("over Sub")
}

func main() {
	wg.Add(2) //加2

	go Add()
	go Add()

	wg.Wait() //等待为0

	time.Sleep(5000 * time.Millisecond) //等待Sub协程

	// over Add
	// over Add
	// over Sub
	// over Sub
}
  • ele使用的是全局的ele
    • fmt.Printf使用的是IO操作
    • for range中使用的协程是无延迟的
    • 也就是说当第一个协程执行fmt.Printf的时候,for循环可能已经结束了,此时的ele已经变成了4
func main() {
	arr := []int{1, 2, 3, 4}
	for _, ele := range arr {
		go func() {
			fmt.Printf("%d ", ele) //用的是协程外面的全局变量ele。输出4 4 4 4
		}()
	}
	time.Sleep(1 * time.Second)
}
  • 闭包:向协程内部传递变量来解决上述问题
func main() {
	arr := []int{1, 2, 3, 4}
	for _, ele := range arr {
		go func(value int) {
			fmt.Printf("%d ", value) //1 4 2 3,这里打印是乱序的没有问题
		}(ele)
	}
	time.Sleep(1 * time.Second)
}
  • sync.Once:有时候需要确保在高并发的场景下有些事情只执行一次,比如加载配置文件、关闭管道等
var oc sync.Once
var a int = 5

func main() {
	go func() {
		oc.Do(func() {
			a++
		})
	}()
	go func() {
		oc.Do(func() {
			a++
		})
	}()

	time.Sleep(time.Second)
	fmt.Println(a) //6
}

2 - panic与defer

  • 何时会发生panic

    • 运行时错误会导致panic,比如数组越界、除0
    • 程序主动调用panic(error)
  • panic会执行什么

    • 逆序执行当前goroutine的defer链(recover从这里介入)
    • 打印错误信息和调用堆栈
    • 调用exit(2)结束整个进程
  • **defer **

    • defer在函数退出前被调用,注意不是在代码的return语句之前执行,因为return语句不是原子操作
    • 如果发生panic,则之后注册的defer不会执行
    • defer服从先进后出原则,即一个函数里如果注册了多个defer,则按注册的逆序执行
    • defer后面可以跟一个匿名函数
func F() {
	defer fmt.Printf("11111 ")
	defer fmt.Printf("22222 ")
	fmt.Printf("GGGGG ")
	defer fmt.Printf("33333 ")
	defer fmt.Printf("44444 ")
	panic("papapa")
	defer fmt.Printf("55555 ")
	fmt.Printf("FFFFF ")
}
func main() {
	go F()
	time.Sleep(time.Second)
	//无panic:GGGGG FFFFF 55555 44444 33333 22222 11111
	//panic:GGGGG 44444 33333 22222 11111
}
  • recover
func F() {
	defer fmt.Printf("11111 ")
	defer fmt.Printf("22222 ")
	fmt.Printf("GGGGG ")
	defer fmt.Printf("33333 ")

	defer func() {
		//从panic发生的地方中途结束本协程
		//协程有中recover,即使panic也不会结束整个进程
		recover()
	}()

	defer fmt.Printf("44444 ")
	panic("papapa")
	defer fmt.Printf("55555 ")
	fmt.Printf("FFFFF ")
}
func main() {
	go F()
	time.Sleep(time.Second)
	fmt.Println("this is main")
	//无panic:GGGGG FFFFF 55555 44444 33333 22222 11111
	//panic:GGGGG 44444 33333 22222 11111
	//无recover有panic:GGGGG 44444 33333 22222 11111
	//有revocer有panic:GGGGG 44444 33333 22222 11111 this is main
}

四、channel的同步与异步‘’

1 - 同步与异步channel

  • 共享内存:很多语言通过共享内存来实现线程间的通信,通过加锁来访问共享数据,如数组、map或结构体。go语言也实现了这种并发模型
    在这里插入图片描述
  • CSP:CSP(communicating sequential processes)讲究的是“以通信的方式来共享内存”,在go语言里channel是这种模式的具体实现
    在这里插入图片描述
  • 异步channelasynChann := make(chan int, 8)
    • channel底层维护一个环形队列(先进先出),make初始化时指定队列的长度
    • 队列满时,写阻塞;队列空时,读阻塞
    • sendx指向下一次写入的位置, recvx指向下一次读取的位置
    • recvq(等待读的goroutine队列)维护因读管道而被阻塞的协程,sendq(等待写的goroutine队列)维护因写管道而被阻塞的协程
      在这里插入图片描述
  • 同步channel:同步管道可以认为队列容量为0,当读协程和写协程同时就绪时它们才会彼此帮对方解除阻塞
    • 创建同步管道:syncChann := make(chan int)
    • 往管理里放数据:syncChann < -1 -> 生产者
    • 从管道取出数据:v := <- syncChann -> 消费者
  • 关于channel的死锁与阻塞
    • Channel满了,就阻塞写;Channel空了,就阻塞读
    • 阻塞之后会交出cpu,去执行其他协程,希望其他协程能帮自己解除阻塞
    • 如果阻塞发生在main协程里,并且没有其他子协程可以执行,那就可以确定“希望永远等不来”,自已把自己杀掉,报一个fatal error:deadlock出来
    • 如果阻塞发生在子协程里,就不会发生死锁,因为至少main协程是一个值得等待的“希望”,会一直等(阻塞)下去
func main() {
	//channel仅作为协程间同步的工具,不需要传递具体的数据,管道类型可以用struct{}
	//空结构体变量的内存占用为0,因此struct{}类型的管道比bool类型的管道还要省内存
	ch := make(chan struct{}, 1)
	ch <- struct{}{} //有1个缓冲可以用,无需阻塞,可以立即执行
	go func() {      //子协程1
		time.Sleep(5 * time.Second) //sleep一个很长的时间
		<-ch                        //如果把本行代码注释掉,main协程5秒钟后会报fatal error
		fmt.Println("sub routine 1 over")
	}()
	//由于子协程1已经启动,寄希望于子协程1帮自己解除阻塞,所以会一直等子协程1执行结束
	//如果子协程1执行结束后没帮自己解除阻塞,则希望完全破灭,报出deadlock
	ch <- struct{}{}
	fmt.Println("send to channel in main routine")
	go func() { //子协程2
		time.Sleep(2 * time.Second)
		ch <- struct{}{} //channel已满,子协程2会一直阻塞在这一行
		fmt.Println("sub routine 2 over")
	}()
	time.Sleep(3 * time.Second)
	fmt.Println("main routine exit")
	// 	sub routine 1 over
	// send to channel in main routine
	// main routine exit
}

2 - 关闭channel

  • 关闭channel的注意点
    • 只有当管道关闭时,才能通过range遍历管道里的数据,否则会发生fatal error
    • 管道关闭后读操作会立即返回,如果缓冲已空会返回“0值”
    • ele, ok := <-ch ok==true代表ele是管道里的真实数据
    • 向已关闭的管道里send数据会发生panic
    • 不能重复关闭管道,不能关闭值为nil的管道,否则都会panic
func main() {
	c := make(chan int, 2)
	c <- 1
	c <- 2

	close(c) //如果不先close会报 -> fatal error: all goroutines are asleep - deadlock!

	// c <- 3 //关闭管道后继续向管道写入发生panic: send on closed channel

	for ele := range c {
		fmt.Printf("%d ", ele) //1 2
	}

	//close channel之后,读操作总是立即返回
	//如果channel里没有元素,则返回对应类型的默认值
	v := <-c
	fmt.Println(v) //0
}
  • channel的应用
    在这里插入图片描述
func upstream(ch chan struct{}) {
	time.Sleep(15 * time.Millisecond)
	fmt.Println("一个上游协程执行结束")
	ch <- struct{}{} //写
}

func downstream(ch chan struct{}) {
	<-ch //读
	fmt.Println("下游协程开始执行")
}

func main() {
	upNum := 4   //上游协程的数量
	downNum := 5 //下游协程的数量

	upCh := make(chan struct{}, upNum)
	downCh := make(chan struct{}, downNum)

	//启动上游协程和下游协程,实际下游协程会先阻塞
	for i := 0; i < upNum; i++ {
		go upstream(upCh) //time.Sleep(15 * time.Millisecond) 延迟执行
	}
	for i := 0; i < downNum; i++ {
		go downstream(downCh) //读阻塞
	}

	//同步点
	for i := 0; i < upNum; i++ {
		<-upCh //主线程读阻塞
	}

	//通过管道让下游协程开始执行
	for i := 0; i < downNum; i++ {
		downCh <- struct{}{}
	}

	time.Sleep(10 * time.Millisecond) //等下游协程执行结束
}
  • 结果分析
一个上游协程执行结束
一个上游协程执行结束
一个上游协程执行结束
一个上游协程执行结束
下游协程开始执行
下游协程开始执行
下游协程开始执行
下游协程开始执行
下游协程开始执行

在这里插入图片描述

五、并发安全性

1 - 资源竞争

  • 资源竞争:多协程并发修改同一块内存,产生资源竞争
    • n++不是原子操作,并发执行时会存在脏写。n++分为3步:取出n,加1,结果赋给n。测试时需要开1000个并发协程才能观察到脏写
      在这里插入图片描述
var n int

func main() {
	wg := sync.WaitGroup{}
	wg.Add(1000)
	for i := 0; i < 1000; i++ {
		go func() {
			defer wg.Done()
			n++
		}()
	}
	wg.Wait()
	fmt.Println(n) //无论运行多少次都不会到达1000
}
  • -rece:go run或go build时添加-race参数检查资源竞争情况go run -race .\main.go

在这里插入图片描述

2 - 原子操作

  • 原子操作
    • func atomic.AddInt32(addr *int32, delta int32) (new int32)
    • func atomic.LoadInt32(addr *int32) (val int32)
var n int32

func main() {
	wg := sync.WaitGroup{}
	wg.Add(1000)
	for i := 0; i < 1000; i++ {
		go func() {
			defer wg.Done()
			atomic.AddInt32(&n, 1)
		}()
	}
	wg.Wait()
	fmt.Println(n)
}

在这里插入图片描述

3 - 读写锁

  • 读写锁
    • var lock sync.RWMutex //声明读写锁,无需初始化
    • lock.Lock() lock.Unlock() //加写锁和释放写锁
    • lock.RLock() lock.RUnlock() //加读锁和释放读锁
var n int32
var lock sync.RWMutex

func main() {
	wg := sync.WaitGroup{}
	wg.Add(1000)
	for i := 0; i < 1000; i++ {
		go func() {
			defer wg.Done()
			lock.Lock()
			n++
			lock.Unlock()
		}()
	}
	wg.Wait()
	fmt.Println(n)
}
  • 写锁可以排斥其他写锁:任意时刻只可以加一把写锁,且不能加读锁
var lock sync.RWMutex

func main() {
	go func() {
		lock.Lock()
		fmt.Println("A lock success")
	}()
	fmt.Println()
	go func() {
		lock.RLock() //阻塞,排斥读锁
		fmt.Println("B lock success")
	}()
	fmt.Println()
	go func() {
		lock.Lock() //阻塞,排斥写锁
		fmt.Println("C lock success")
	}()
	time.Sleep(time.Second)
}

在这里插入图片描述

  • 没加写锁时,可以同时加多把读锁,读锁加上之后不能再加写锁
var lock sync.RWMutex

func main() {
	go func() {
		lock.RLock()
		fmt.Println("A lock success")
	}()
	fmt.Println()
	go func() {
		lock.RLock()
		fmt.Println("B lock success")
	}()
	fmt.Println()
	go func() {
		lock.Lock() //阻塞,排斥写锁
		fmt.Println("C lock success")
	}()
	time.Sleep(time.Second)
}

在这里插入图片描述

4 - 容器的并发安全

  • 数组、slice、struct:允许并发修改(可能会脏写)
//切片和数组一样
func main() {
	lst := make([]int, 5)
	go func() {
		for i := 0; i < len(lst); i += 1 {
			time.Sleep(10 * time.Millisecond)
			lst[i] = 888
		}
	}()
	go func() {
		for i := 0; i < len(lst); i += 1 {
			time.Sleep(10 * time.Millisecond)
			lst[i] = 555
		}
	}()
	time.Sleep(time.Second)
	fmt.Println(lst)
}

// PS C:\develop_project\go_project\proj1> go run .\main.go
// [888 888 888 888 888]
// PS C:\develop_project\go_project\proj1> go run .\main.go
// [888 555 555 555 555]
// PS C:\develop_project\go_project\proj1> go run .\main.go
// [555 555 555 555 555]
// PS C:\develop_project\go_project\proj1> go run .\main.go
// [555 555 555 555 555]
// PS C:\develop_project\go_project\proj1> go run .\main.go
// [888 555 555 555 555]
// PS C:\develop_project\go_project\proj1> go run .\main.go
// [555 888 888 888 888]

//struct
func main() {
	type Student struct {
		Age  int
		Name string
	}
	stu := new(Student)
	go func() {
		for i := 0; i < 10; i += 1 {
			time.Sleep(10 * time.Millisecond)
			stu.Age = 18
			time.Sleep(10 * time.Millisecond)
			stu.Name = "Jack"
		}
	}()
	go func() {
		for i := 0; i < 10; i += 1 {
			time.Sleep(10 * time.Millisecond)
			stu.Age = 11
			time.Sleep(10 * time.Millisecond)
			stu.Name = "Tom"
		}
	}()
	time.Sleep(time.Second)
	fmt.Println(stu)
}

// PS C:\develop_project\go_project\proj1> go run .\main.go
// &{18 Jack}
// PS C:\develop_project\go_project\proj1> go run .\main.go
// &{11 Jack}
// PS C:\develop_project\go_project\proj1> go run .\main.go
// &{11 Tom}
  • map:并发修改map有时会发生panic
    • fatal error: concurrent map writes
func main() {
	mp := make(map[int]int, 10)
	go func() {
		for i := 0; i < 100; i += 1 {
			mp[i] = i
		}
	}()
	go func() {
		for i := 0; i < 100; i += 1 {
			mp[i] = i
		}
	}()
	time.Sleep(time.Second)
	fmt.Println(mp)
}

在这里插入图片描述

  • sync.Map:如果需要并发修改map请使用sync.Map
func main() {
	var mp sync.Map
	go func() {
		for i := 0; i < 100; i += 1 {
			mp.Store(i, i)
		}
	}()
	go func() {
		for i := 0; i < 100; i += 1 {
			mp.Store(i, i)
		}
	}()
	time.Sleep(time.Second)
	fmt.Println(mp.Load(0))
}

六、多路复用

  • 操作系统级的I/O模型有
    • 阻塞I/O
    • 非阻塞I/O
    • 信号驱动I/O
    • 异步I/O
    • 多路复用I/O:
      • Linux下,一切皆文件
      • 包括普通文件、目录文件、字符设备文件(键盘、鼠标)、块设备文件(硬盘、光驱)、套接字socket等等
      • 文件描述符(File descriptor,FD)是访问文件资源的抽象句柄,读写文件都要通过它
      • 文件描述符就是个非负整数,每个进程默认都会打开3个文件描述符:0标准输入、1标准输出、2标准错误
      • 由于内存限制,文件描述符是有上限的,可通过ulimit –n查看,文件描述符用完后应及时关闭

1 - 阻塞I/O

在这里插入图片描述

2 - 非阻塞I/O

  • 阻塞I/O与非阻塞I/O的区别
    • 阻塞I/O在阻塞的时候是不会处理其他事务一直阻塞等待
    • 非阻塞I/O在阻塞的时候可以先处理其他事务,提高CPU的利用率
      在这里插入图片描述
  • read和write默认是阻塞模式
    • ssize_t read(int fd, void *buf, size_t count);
    • ssize_t write(int fd, const void *buf, size_t nbytes);
  • 通过系统调用fcntl可将文件描述符设置成非阻塞模式
    • int flags = fcntl(fd, F_GETFL, 0);
    • fcntl(fd, F_SETFL, flags | O_NONBLOCK);

3 - 多路复用I/O

  • **多路复用I/O **
    • select系统调用可同时监听1024个文件描述符的可读或可写状态
    • poll用链表存储文件描述符,摆脱了1024的上限
    • 各操作系统实现了自己的I/O多路复用函数,如epoll、 evport 和kqueue等
      在这里插入图片描述
  • go的多路复用I/O
    • go多路复用函数以netpoll为前缀,针对不同的操作系统做了不同的封装,以达到最优的性能
    • 在编译go语言时会根据目标平台选择特定的分支进行编译
      在这里插入图片描述
LOOP:
	for {
		select { //同时监听多个channel,谁准备好就执行谁
		case n := <-countCh:
			fmt.Println(n)
		case <-finishCh:
			fmt.Println("finish")
			break LOOP //break LOOP  退出for循环,在使用for select的时,单独一个break无法退出
		case <-abortCh:
			fmt.Println("abort")
			break LOOP //break LOOP  退出for循环
		}
	}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/449612.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

差分(一维+二维)

类似于数学中的求导和积分&#xff0c;差分可以看成前缀和的逆运算。 前缀和我们是求原数组的前缀和&#xff0c;这里是把原数组当成前缀和&#xff0c;构造一个差分数组来运算 以一维为例&#xff0c;如原数组为a[1],a[2],a[3]...a[n] 前缀和的思想是构造st[1]a[1],st[2]a[…

【C++】vector的使用

文章目录 1. 主要结构2. 构造函数与复制重载3. 迭代器4. 容量相关1.容量读取2.容量修改 5. 数据访问6. 数据修改1. 尾插尾删2.任意位置的插入删除 7.其他接口 在之前我们学习了string的使用与模拟实现&#xff0c;在参考文档中可以发现&#xff0c;vector和string以及其他的容器…

I/O 设备

CPU有两种方法访问IO设备 都是基于PMIO的&#xff0c;Port Mapped I/O 给IO总线上的寄存器编号&#xff0c;CPU向IO总线请求写入或读取数据 &#xff08;x86&#xff09;给特定的内存地址对应上目标IO设备&#xff0c;当CPU读取这段内存的时候&#xff0c;就会把访问转发给IO…

微服务 - Consul服务注册中心

概述 上篇说到构建良好的架构&#xff0c;依托于基础设施建设(自动化测试、自动化部署、服务监控&#xff0c;服务发现、配置中心等等)&#xff0c;决定成败的往往是基础设施建设&#xff0c;所以从搭建一个注册中心和配置中心开始我们新一阶段的启程。 注册中心 注册中心选型…

Cordic算法原理详解

目录 坐标旋转分析 Cordic算法原理 应用举例1&#xff1a;求sin值与cos值 应用举例2&#xff1a;求反正切值 cosθ的还原补偿 坐标旋转数字计算机CORDIC(COordinate Rotation DIgital Computer)算法&#xff0c;通过移位和加减运算&#xff0c;能递归计算常用函数值&#…

《Netty》从零开始学netty源码(四十一)之PoolChunk.runsAvail

runsAvail runsAvail用于记录long型的指针值&#xff0c;是一个LongPriorityQueue数组&#xff0c;LongPriorityQueue的结构如下&#xff1a; array数组用于存储handle的值&#xff0c;其中下标对应SizeClasses中pageIdx&#xff0c;size为array数组的大小&#xff0c;size的大…

1.13|1.14|1.15|1.6、GDB调试

1.13|1.14|1.15|1.6、GDB调试 1.13、GDB调试&#xff08;1&#xff09;&#xff0c;GDB调试&#xff08;2&#xff09;1. 什么是GDB2. 准备工作3. GDB命令—启动、推出、查看代码实际操作①用list查看代码 1.15、GDB调试&#xff08;3&#xff09;1. GDB命令—断点操作实际操作…

Redis 快速上手 Java 增删改查(包含 RedisTemplateConfig 的编写)

一&#xff1a;Redis 数据类型 先了解 redis 的五种基本数据类型。 String 字符串类型&#xff1a;name: "value1"List 列表&#xff1a;names: ["value1", "value2", "value2"]Set 集合&#xff1a;names: ["value1", &qu…

多源迁移学习网络补充知识用于具有不可见故障的机器智能诊断

**摘要&#xff1a;**当前基于深度迁移学习的故障诊断的大多数成功需要两个假设&#xff1a;1&#xff09;源机器的健康状态集合应当与目标机器的健康状态集合重叠;2&#xff09;目标机器样本的数量跨健康状态平衡。然而&#xff0c;这样的假设在工程场景中是不现实的&#xff…

【闲聊杂谈】HTTPS原理详解

HTTPS和HTTP的区别 HTTP虽然使用极为广泛, 但是却存在不小的安全缺陷, 主要是其数据的明文传送和消息完整性检测的缺乏, 而这两点恰好是网络支付, 网络交易等新兴应用中安全方面最需要关注的。 关于 HTTP的明文数据传输, 攻击者最常用的攻击手法就是网络嗅探, 试图从传输过程…

Redis高可用高性能缓存的应用系列06 - 热Key,大Key,并发竞争解决方案

概述 终于迎来了Redis系列的尾声&#xff0c;本文针对Redis常遇到的热Key&#xff0c;大Key&#xff0c;并发竞争解决方案进行介绍。 热Key 什么是热key?当一个key的访问量明显大于其他key的时候&#xff0c;他就可以被称为热key。 热Key带来的问题 热key占用大量的CPU资…

黑马在线教育数仓实战7

1. hive的相关的优化 1.1 hive的相关的函数(补充说明) if函数: 作用: 用于进行逻辑判断操作语法: if(条件, true返回信息,false返回信息) 注意: if函数支持嵌套使用 nvl函数: 作用: null值替换函数格式: nvl(T value, T default_value) COALESCE函数 作用: 非空查找函数:格式…

Windows安装GPU环境CUDA、深度学习框架Tensorflow和Pytorch

Windows安装GPU环境CUDA、深度学习框架Tensorflow和Pytorch 1、未安装CUDA使用tensorflow报错 import tensorflow as tf2022-03-06 15:14:38.869955: W tensorflow/stream_executor/platform/default/dso_loader.cc:60] Could not load dynamic library cudart64_110.dll; dl…

Django | 一文完美解决admin增加新用户只有用户名密码和确认密码的问题

文章目录 如图所示&#xff0c;下面给出解决方案&#xff1a; 如果您使用 使用 Django 默认的后台管理界面添加用户时&#xff0c;只看到了三个字段&#xff08;通常是 username、password和 repassword&#xff09;&#xff0c;那么可以通过定义 add_fieldsets 属性来增加更多…

【JUC】原子操作类

【JUC】原子操作类 文章目录 【JUC】原子操作类1. 原子操作类1.1 基本类型原子类1.2 数组类型原子类1.3 引用类型原子类1.3.1 AtomicReference1.3.2 AtomicStampedReference1.3.3 AtomicMarkableReference 1.4 对象的属性修改原子类 1. 原子操作类 原子操作类如下所示&#xf…

【Linux】进程间通信 --- 管道 共享内存 消息队列 信号量

等明年国庆去西藏洗涤灵魂&#xff0c;laozi不伺候这无聊的生活了 文章目录 一、进程间通信1.什么是通信&#xff1f;&#xff08;IPC&#xff09;2.为什么要有通信&#xff1f;&#xff08;多进程协同&#xff09;3.如何进行通信&#xff1f; 二、基于文件系统的管道通信1.通信…

acwing17给了一个头节点,从尾到头输出链表的元素,顺便练练容器

方法一 建立一个数组&#xff0c;从头到尾遍历一遍链表&#xff0c;然后将链表的每个元素的值赋给数组 犯了一个错误 新建的vector容器是一个可变长的数组&#xff0c;要想像数组下标那样访问前提是这个下标所指向的元素得存在&#xff0c;这也就跟那个声明一维数组得写出长度来…

rk3568 适配摄像头 (CIF协议)

rk3568 适配摄像头 (CIF协议) 在RK3568处理器中&#xff0c;支持CIF协议的摄像头可以通过CSI接口连接到处理器&#xff0c;实现视频数据的采集和处理。同时&#xff0c;RK3568还支持多种图像处理算法和编解码器&#xff0c;可以对采集到的视频数据进行实时处理和压缩&#xff…

[Golang实战] 带你入手gin框架使用,以项目为例

&#x1f61a;一个不甘平凡的普通人&#xff0c;致力于为Golang社区和算法学习做出贡献&#xff0c;期待您的关注和认可&#xff0c;陪您一起学习打卡&#xff01;&#xff01;&#xff01;&#x1f618;&#x1f618;&#x1f618; &#x1f917;专栏&#xff1a;算法学习 &am…

Dear ImGui结合CMake实现基于GLFW和OpenGL3的入门级hello world代码

Dear ImGui结合CMake实现基于GLFW和OpenGL3的入门级hello world代码 如需转载请标明出处&#xff1a;https://blog.csdn.net/itas109 技术交流&#xff1a;129518033 环境&#xff1a; OS: windows 10 / Ubuntu 22.04 imgui: 1.89.5 glw: 3.3.8前言 Dear ImGui 是一个 用于…