百度面试手撕 go context channel部分学习

news2024/12/21 6:41:49

题目

手撕 对无序的切片查询指定数 使用context进行子协程的销毁 并且进行超时处理。

全局变量定义

var (
	startLoc = int64(0) // --- 未处理切片数据起始位置
	endLoc = int64(0) // --- 切片数据右边界 避免越界
	offset   = int64(0) // --- 根据切片和协程数量 在主线程 动态设置
	target   = 42 // --- 设置的目标值
	mu       sync.Mutex // --- 避免并发冲突使用的全局锁
)

1.并发处理

1.1 使用atomic原子操作

使用CAS操作解决并发问题(不使用锁) 效率上和使用全局锁在 100000 上几乎没差别

// --- 使用atomic原子操作
start = atomic.LoadInt64(&startLoc)
end = start + offset
if end > endLoc {
	end = endLoc
}
// 应该不会出现ABA问题
if ok := atomic.CompareAndSwapInt64(&startLoc, start, end); ok == false {
	continue
}

1.2 使用全局锁

mu.Lock()
start = startLoc
end = start + offset
startLoc = end
mu.Unlock()
if start >= endLoc {
	return
}
if end > endLoc {
	end = endLoc
}

1.3主线程手动切片全部代码

package main

import (
	"context"
	"fmt"
	"sync"
	"sync/atomic"
	"time"
)

func find(nums []int, ctx context.Context, wg *sync.WaitGroup, target int, start, end int64) {
	defer wg.Done()
	for {
		select {
		case <-ctx.Done():
			// 如果接收到取消信号,退出协程
			return
		default:
			for i := start; i < end; i++ {
				if nums[i] == target {
					// 使用 atomic 以确保线程安全
					atomic.StoreInt32(&valid, 1)
					return
				}
			}
			return
		}
	}
}

var valid int32

func main() {
	sliceLen := int64(1000000)
	// 创建一个背景上下文和一个取消功能
	ctx := context.Background()

	// 假设 ddl 是一个固定的截止时间
	ddl := time.Now().Add(10 * time.Second) // 假设 5 秒钟后超时
	newCtx, cancel := context.WithDeadline(ctx, ddl)

	// 创建一个较大的切片 nums 并初始化
	nums := make([]int, sliceLen)
	// 初始化切片为随机数据,例如从 1 到 100,值为42的即为目标
	for i := 0; i < len(nums); i++ {
		nums[i] = i
	}
	offset := sliceLen / 10
	startLoc := int64(0)

	startTime := time.Now()
	// 使用 WaitGroup 来等待所有协程完成
	var wg sync.WaitGroup
	// 启动多个协程进行查找
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go find(nums, newCtx, &wg, 42, startLoc, startLoc+offset)
		startLoc = startLoc + offset
	}

	// 等待结果
	go func() {
		wg.Wait()
		cancel() // 等待所有协程结束后,调用 cancel
	}()

	// 检查结果
	select {
	case <-newCtx.Done():
		if atomic.LoadInt32(&valid) == 1 {
			fmt.Println("Found target!")
		} else {
			fmt.Println("Timeout or not found.")
		}
	}

	duration := time.Since(startTime)
	fmt.Printf("程序运行时间: %s\n", duration)
}

1.4 采取锁处理 & 原子操作 全部代码

package main

import (
	"context"
	"fmt"
	"sync"
	"sync/atomic"
	"time"
)

var (
	startLoc = int64(0)
	endLoc   = int64(0)
	offset   = int64(0)
	target   = 42
	mu       sync.Mutex
)

func find(nums []int, ctx context.Context, wg *sync.WaitGroup) {
	defer wg.Done()
	var start, end int64
	for {
		select {
		case <-ctx.Done():
			// 如果接收到取消信号,退出协程
			return
		default:
			// --- 使用全局锁
			// 查找区间
			//mu.Lock()
			//start = startLoc
			//end = start + offset
			//startLoc = end
			//mu.Unlock()
			//if start >= endLoc {
			//	return
			//}
			//if end > endLoc {
			//	end = endLoc
			//}

			// --- 使用atomic原子操作
			start = atomic.LoadInt64(&startLoc)
			end = start + offset
			if end > endLoc {
				end = endLoc
			}
			if start >= endLoc {
				return
			}
			// 应该不会出现ABA问题
			if ok := atomic.CompareAndSwapInt64(&startLoc, start, end); ok == false {
				//time.Sleep(100)
				continue
			}

			for i := start; i < end; i++ {
				if nums[i] == target {
					// 使用 atomic 以确保线程安全
					atomic.StoreInt32(&valid, 1)
					return
				}
			}
		}
	}
}

var valid int32

func main() {
	sliceLen := int64(100000)
	// 创建一个背景上下文和一个取消功能
	ctx := context.Background()

	// 假设 ddl 是一个固定的截止时间
	ddl := time.Now().Add(10 * time.Second) // 假设 5 秒钟后超时
	newCtx, cancel := context.WithDeadline(ctx, ddl)

	// 创建一个较大的切片 nums 并初始化
	nums := make([]int, sliceLen)
	endLoc = sliceLen
	// 初始化切片为随机数据,例如从 1 到 100,值为42的即为目标
	for i := 0; i < len(nums); i++ {
		nums[i] = i
	}

	startTime := time.Now()
	// 使用 WaitGroup 来等待所有协程完成
	var wg sync.WaitGroup
	offset = int64(sliceLen / 10)
	// 启动多个协程进行查找
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go find(nums, newCtx, &wg)
	}

	// 等待结果
	go func() {
		wg.Wait()
		cancel() // 等待所有协程结束后,调用 cancel
	}()

	// 检查结果
	select {
	case <-newCtx.Done():
		if atomic.LoadInt32(&valid) == 1 {
			fmt.Println("Found target!")
		} else {
			fmt.Println("Timeout or not found.")
		}
	}

	duration := time.Since(startTime)
	fmt.Printf("程序运行时间: %s\n", duration)
}

 2.Context部分

2.1 context是并发安全

创建的初始context有两种 TODO()和Background(),查看内部结构体, 实际都是emptyCtx。

Background()创建的上下文通常被认为整个请求的顶级 Context,而TODO()创建的通常被认为是暂时的、未确定的 Context。

func Background() Context {
	return backgroundCtx{}
}

func TODO() Context {
	return todoCtx{}
}
1. 传值Value

直接对父context进行包装,并不会修改父context

type valueCtx struct {
	Context
	key, val any
}

func WithValue(parent Context, key, val any) Context {
	if parent == nil {
		panic("cannot create context from nil parent")
	}
	if key == nil {
		panic("nil key")
	}
	if !reflectlite.TypeOf(key).Comparable() {
		panic("key is not comparable")
	}
	return &valueCtx{parent, key, val}
}
2. 设置超时时间 WithDeadline
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
	return WithDeadlineCause(parent, d, nil)
}

2.2 context的信号传递

以cancel部分举例说明

1. 设置超时时间

设置取消函数的接口主要分为下列几种情况:

  1. 父Ctx为nil, 抛出异常
  2. 父Ctx具有超时时间,且比设置的超时时间更早结束,则新建CancelCtx加入父Ctx监听列表,且返回该新建CancelCtx。
  3. 设置新的包含超时时间的timerCtx(内部继承了cancelCtx结构体),加入父Ctx的监听列表,检查是否已经超时, 超时则取消该上下文, 没超时则设置计时器,等待取消。
func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc) {
	if parent == nil {
		panic("cannot create context from nil parent")
	}
	if cur, ok := parent.Deadline(); ok && cur.Before(d) {
		// The current deadline is already sooner than the new one.
		return WithCancel(parent)
	}
	c := &timerCtx{
		deadline: d,
	}
	c.cancelCtx.propagateCancel(parent, c)
	dur := time.Until(d)
	if dur <= 0 {
		c.cancel(true, DeadlineExceeded, cause) // deadline has already passed
		return c, func() { c.cancel(false, Canceled, nil) }
	}
	c.mu.Lock()
	defer c.mu.Unlock()
	if c.err == nil {
		c.timer = time.AfterFunc(dur, func() {
			c.cancel(true, DeadlineExceeded, cause)
		})
	}
	return c, func() { c.cancel(true, Canceled, nil) }
}

2.设置子Ctx监听父Ctx

上下文取消传播:propagateCancel 的核心目的是将父上下文的取消信号(及其取消原因)传递给子上下文。不同的父上下文类型(如 *cancelCtx 或实现了 AfterFunc 方法的上下文)会采取不同的处理方式。
并发处理:通过 goroutines.Add(1) 和新的 goroutine 来监听父上下文的取消事件,确保并发场景下的取消传播。

其中分为三种情况:

  1. 父Ctx未设置Done ,则无需监听
  2. 父Ctx设置了回调函数
  3. 父Ctx类型是*cancelCtx,则把子Ctx加入自身map中,每个子Ctx都会开启协程监听父Ctx信号,同步取消自身。

主要就是依赖Channel进行信号传递

func (c *cancelCtx) propagateCancel(parent Context, child canceler) {
	c.Context = parent

	done := parent.Done()
	if done == nil {
		return // parent is never canceled
	}

	select {
	case <-done:
		// parent is already canceled
		child.cancel(false, parent.Err(), Cause(parent))
		return
	default:
	}

	if p, ok := parentCancelCtx(parent); ok {
		// parent is a *cancelCtx, or derives from one.
		p.mu.Lock()
		if p.err != nil {
			// parent has already been canceled
			child.cancel(false, p.err, p.cause)
		} else {
			if p.children == nil {
				p.children = make(map[canceler]struct{})
			}
			p.children[child] = struct{}{}
		}
		p.mu.Unlock()
		return
	}

	if a, ok := parent.(afterFuncer); ok {
		// parent implements an AfterFunc method.
		c.mu.Lock()
		stop := a.AfterFunc(func() {
			child.cancel(false, parent.Err(), Cause(parent))
		})
		c.Context = stopCtx{
			Context: parent,
			stop:    stop,
		}
		c.mu.Unlock()
		return
	}

	goroutines.Add(1)
	go func() {
		select {
		case <-parent.Done():
			child.cancel(false, parent.Err(), Cause(parent))
		case <-child.Done():
		}
	}()
}

参考链接:

Go 语言并发编程与 Context | Go 语言设计与实现

3.channel部分

3.1channel底层结构

在有缓冲区的channel部分,数据使用环形链表进行存储,存储有变量记录有效数据区域。

type hchan struct {
 qcount   uint           // Channel 中的元素个数
 dataqsiz uint           // Channel 中的循环队列的长度
 buf      unsafe.Pointer // Channel 的缓冲区数据指针
 elemsize uint16
 closed   uint32
 elemtype *_type // element type
 sendx    uint   // Channel 的发送操作处理到的位置
 recvx    uint   // Channel 的接收操作处理到的位置
 recvq    waitq  // 等待消息的双向链表
 sendq    waitq  // 发生消息双向链表

 // lock protects all fields in hchan, as well as several
 // fields in sudogs blocked on this channel.

 // Do not change another G's status while holding this lock
 // (in particular, do not ready a G), as this can deadlock
 // with stack shrinking.
 lock mutex
}

// 创建双向链表 构造等待消息 或 发生消息的goroutine的双向链表
type waitq struct {
 first *sudog  
 last  *sudog
}

有缓冲区

无缓冲区

3.2 对于不同的channel进行读入读出的不同情况

如果给一个 nil 的 channel 发送数据,会造成永远阻塞。

如果从一个 nil 的 channel 中接收数据,也会造成永久阻塞。

给一个已经关闭的 channel 发送数据, 会引起 panic。

从一个已经关闭的 channel 接收数据, 如果缓冲区中为空,则返回一个零值。

同时分为有缓冲区和无缓冲区两种,前者是异步的,在缓冲区未满时,可以持续输入,不会阻塞,直到缓冲区满;后者则为有goroutine输入,等待有协程进行数据消费,否则持续阻塞。

对nil的channel不可操作。

参考链接:

https://www.cnblogs.com/Paul-watermelon/articles/17484439.html

Go 语言 Channel 实现原理精要 | Go 语言设计与实现 (draveness.me)

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

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

相关文章

Otsu 二值化算法:原理、实现与应用

摘要&#xff1a; 本文深入探讨了 Otsu 二值化算法&#xff0c;详细阐述其原理&#xff0c;包括类间方差的计算与阈值确定机制。分别给出了该算法在 C#、Python 和 C 中的实现代码示例&#xff0c;并对代码进行了详细注释与分析。此外&#xff0c;还探讨了 Otsu 二值化算法在图…

uniApp使用腾讯地图提示未添加maps模块

uniApp使用腾讯地图&#xff0c;打包提示未添加maps模块解决方案 这是报错信息&#xff0c;在标准基座运行的时候是没问题的&#xff0c;但是打包后会提示未添加&#xff0c;可以通过在mainfest里面把地图插件上腾讯地图的key更换高德地图的key&#xff0c;定位服务可以继续用腾…

Deepin/Linux clash TUN模式不起作用,因网关导致的问题的解决方案。

网关导致的问题的解决方案 查看路由 ip route寻找默认路由 默认路由应当为Mihomo default dev Mihomo scope link 如果不是&#xff0c;则 sudo ip route add default dev Mihomo在clash TUN开关状态发生变化时&#xff0c;Mihomo网卡会消失&#xff0c;所以提示找不到网卡…

scala中正则表达式的使用

正则表达式&#xff1a; 基本概念 在 Scala 中&#xff0c;正则表达式是用于处理文本模式匹配的强大工具。它通过java.util.regex.Pattern和java.util.regex.Matcher这两个 Java 类来实现&#xff08;因为 Scala 运行在 Java 虚拟机上&#xff0c;可以无缝使用 Java 类库&…

apache应用(客户机地址限制、用户授权限制、日志分割、AWStats日志分析)

目录 一、 客户机地址限制 二、 用户授权限制 三、 日志分割 使用rotatelogs分割工具 使用第三方工具cronolog 四、 AWStats日志分析 具体的apache软件安装可以阅读我之前的文章apache安装https://blog.csdn.net/m0_68472908/article/details/139348739?spm1001.2014.300…

护士资格实践题库(含解析)

1.患者女&#xff0c;30岁。诊断类风湿关节炎入院&#xff0c;经使用药物治疗后患者关节疼痛减轻&#xff0c;但出现体重增加、满月脸、向心性肥胖。提示存在何种药物的副作用&#xff08; &#xff09; A.泼尼松 B.环磷酰胺 C.硫唑嘌呤 D.吲哚美辛 E.阿司匹林 【答案】…

网络安全概论——防火墙原理与设计

一、防火墙概述 防火墙是一种装置&#xff0c;它是由软件/硬件设备组合而成&#xff0c;通常处于企业的内部局域网与 Internet 之间&#xff0c;限制 Internet 用户对内部网络的访问以及管理内部用户访问 Internet 的权限。换言之&#xff0c;一个防火墙在一个被认为是安全和可…

接口测试-Fidder及jmeter使用

一、接口测试的基础 1.接口的含义 也叫做API&#xff0c;是一组定义、程序及协议的集合&#xff0c;提供访问一组例程的能力&#xff0c;无需访问源码获理解内部工作细节 2.接口的分类 代码内部的接口&#xff0c;程序模块间的接口&#xff0c;对于程序接口测试&#xff0c;需…

postman设置cookie

postman发送请求的时候&#xff0c;如何顺带cookie? 示例&#xff1a; Cookie: locale_areazh; contryzh_cn;

Java-31 深入浅出 Spring - IoC 基础 启动IoC XML与注解结合的方式 配置改造 applicationContext.xml

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 大数据篇正在更新&#xff01;https://blog.csdn.net/w776341482/category_12713819.html 目前已经更新到了&#xff1a; MyBatis&#xff…

jmeter连接mysql

查询mysql数据库版本 SELECT VERSION(); 下载jmeter mysql 驱动jar包&#xff0c;版本低于mysql版本&#xff0c;放在jmeter的lib 路径下 MySQL :: Download MySQL Connector/J (Archived Versions) 添加JDBC Connection Configuration 填写 variable name 及数据库信息 注意…

计算机网络基础(2):网络安全/ 网络通信介质

1. 网络安全威胁 网络安全&#xff1a;目的就是要让网络入侵者进不了网络系统&#xff0c;及时强行攻入网络&#xff0c;也拿不走信息&#xff0c;改不了数据&#xff0c;看不懂信息。 事发后能审查追踪到破坏者&#xff0c;让破坏者跑不掉。 网络威胁来自多方面&#xff1a…

数据分析实战—IMDB电影数据分析

1.实战内容 1.加载数据到movies_df&#xff0c;输出前5行&#xff0c;输出movies_df.info(),movies_df.describe() # &#xff08;1&#xff09;加载数据集&#xff0c;输出前5行 #导入库 import pandas as pd import numpy as np import matplotlib import matplotlib.pyplo…

windwos defender实现白名单效果(除了指定应用或端口其它一律禁止)禁止服务器上网

一、应用场景说明 当我们的一台windows服务器中毒&#xff0c;变成别人肉鸡&#xff0c;不断向外请示非法网站或攻击其它服务器。 要彻底清除相关木马或病毒往往需要的时间比较长&#xff0c;比较有效的方法是禁止服务器主动向外发包除了网站端口和远程程序除外。 其实这就是一…

线性代数基础与应用:基底 (Basis) 与现金流及单期贷款模型(中英双语)

具体请参考&#xff1a;https://web.stanford.edu/~boyd/vmls/ 下面的例子来源于这本书。 线性代数基础与应用&#xff1a;基底 (Basis) 与现金流及单期贷款模型 在线性代数中&#xff0c;基底&#xff08;Basis&#xff09;是一个重要的概念&#xff0c;广泛应用于信号处理、…

微信小程序的消息头增加的字段不能有下滑线,字段大写字母自动转换消息字母

微信小程序的消息头增加的字段不能有下滑线&#xff0c;字段大写字母自动转换消息字母。这个是微信小程序的坑。 正式环境&#xff1a; 微信小程序的消息头增加了一个字段device_id,结果node.js打印出来的字段没有该字段。 [2024-12-20T09:45:54.476] [DEBUG] app - ctx.head…

【Java项目】基于SpringBoot的【旅游管理系统 】

【Java项目】基于SpringBoot的【旅游管理系统 】 技术简介&#xff1a;本系统使用JAVA语言开发&#xff0c;采用B/S架构、Spring Boot框架、MYSQL数据库进行开发设计。 系统简介&#xff1a;&#xff08;1&#xff09;管理员功能&#xff1a;可以管理个人中心、用户管理、景区分…

游戏引擎学习第55天

仓库: https://gitee.com/mrxiao_com/2d_game 介绍 今天的主题是让世界存储真正实现稀疏化&#xff0c;即便当前效率可能并不高。我们计划花一些时间处理这个问题&#xff0c;并探讨相关的成本。稀疏化世界存储是接下来的重要步骤&#xff0c;为此需要逐步实施。 修复 SetCa…

每日十题八股-2024年12月19日

1.Bean注入和xml注入最终得到了相同的效果&#xff0c;它们在底层是怎样做的&#xff1f; 2.Spring给我们提供了很多扩展点&#xff0c;这些有了解吗&#xff1f; 3.MVC分层介绍一下&#xff1f; 4.了解SpringMVC的处理流程吗&#xff1f; 5.Handlermapping 和 handleradapter有…

Linux限制root 用户的远程登录(安全要求)

前言&#xff1a;现在基本用户主机都不允许使用root来操作&#xff0c;所以本文通过创建新用户&#xff0c;并限制root用户的ssh来解决这个问题 1. 创建新账户 aingo 首先&#xff0c;使用 root 账户登录系统。 sudo useradd aingo设置 aingo 账户密码&#xff1a; sudo pa…