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

题目

手撕 对无序的切片查询指定数 使用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)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

任务一登录安全加固

1 &#xff08;1&#xff09;、&#xff08;2&#xff09; secpol.msc打开本地安全策略 2 &#xff08;1&#xff09; DCOM: 在安全描述符定义语言(SDDL)语法中的计算机访问限制 没有定义 DCOM: 在安全描述符定义语言(SDDL)语法中的计算机启动限制 没有定义 Microsoft 网络服…

无人机推流直播平台EasyDSS视频技术如何助力冬季森林防火

冬季天干物燥&#xff0c;大风天气频繁&#xff0c;是森林火灾的高发期。相比传统的人力巡查&#xff0c;无人机具有更高的灵敏度和准确性&#xff0c;尤其在夜间或浓雾天气中&#xff0c;依然能有效地监测潜在火源。 无人机可以提供高空视角和实时图像传输&#xff0c;帮助巡…

写SQL太麻烦?免费搭建 Text2SQL 应用,智能写 SQL | OceanBase AI 实践

自OceanBase 4.3.3版本推出以来&#xff0c;向量检索的能力受到了很多客户的关注&#xff0c;也纷纷表达希望OB能拓展更多 多模数据库大模型 的AI应用实践。 在上篇文章 &#x1f449; OceanBase LLM&#xff0c;免费构建你的专属 AI 助手 &#xff0c;我们介绍了如何去搭建一…

Halcon 机器视觉案例 之 药剂液面高度测量

第二篇 机器视觉案例 之 药剂液面高度测量 文章目录 第二篇 机器视觉案例 之 药剂液面高度测量1.案例要求2.实现思路2.1获得液面的位置&#xff1a;2.1.1 获得每支药剂的位置坐标2.1.2 根据药剂的横坐标设置卡尺工具助手找到每一个液面的位置 2.2 获得基准线的位置&#xff1a;…

使用k6进行MongoDB负载测试

1.安装环境 安装xk6-mongo扩展 ./xk6 build --with github.com/itsparser/xk6-mongo 2.安装MongoDB 参考Docker安装MongoDB服务-CSDN博客 连接成功后新建test数据库和sample集合 3.编写脚本 test_mongo.js import xk6_mongo from k6/x/mongo;const client xk6_mongo.new…

LabVIEW随机扫描成像系统

利用LabVIEW开发了一套随机扫描成像系统&#xff0c;利用硬件时钟实现声光偏转器&#xff08;AOD&#xff09;的频率控制与信号采集之间的高速时间同步。系统利用了高精度的时钟同步技术&#xff0c;确保了成像精度和重复性&#xff0c;从而有效提高了成像速度和质量。 项目背景…

29. 多线程编程

一、什么是线程 线程&#xff08;thread&#xff09;它们是同一个进程下执行的&#xff0c;并共享相同的下上文。线程包括开始、执行顺序和结束三部分。它有一个指令指针&#xff0c;用于记录当前运行的上下文。当其它线程运行时&#xff0c;它可以被抢占&#xff08;中断&…

精准采集整车信号:风丘混合动力汽车工况测试

一 背景 混合动力汽车是介于纯电动汽车与燃油汽车两者之间的一种新能源汽车。它既包含纯电动汽车无污染、启动快的优势&#xff0c;又拥有燃油车续航便捷、不受电池容量限制的特点。在当前环境下&#xff0c;混合动力汽车比纯电动汽车更符合目前的市场需求。 然而&#xff0c…

《军工记忆》第二季播出,科技创新铸国之重器

2019年8月1日晚20点&#xff0c;《军工记忆》第二季在央视纪录频道&#xff08;CCTV-9&#xff09;播出&#xff0c;第一集《第一颗氢弹》首当其冲&#xff0c;为我们生动描绘了氢弹研制过程的艰难岁月&#xff0c;重现中国军工事业的漫漫长路&#xff0c;科技创新铸国之重器。…

QT5.9.9+ARM交叉编译开发环境搭建【详细步骤】

文章目录 1.前言2.Linaro编译器下载2.qt-everywhere编译安装3.Linux下QT Creator安装4.交叉编译环境配置5.QTARM程序打包参考文献 1.前言 本文将以32位的ARM开发板为例&#xff0c;搭建基于Ubuntu 16.04虚拟机的QTARM开发环境&#xff0c;使用的软件如下&#xff1a; 序号软件…

深入解析 StarRocks 物化视图:全方位的查询改写机制

小编导读&#xff1a; 本文将重点介绍如何利用物化视图进行查询改写。文章将全面介绍物化视图的基本原理、关键特性、应用案例、使用场景、代码细节以及主流大数据产品的物化视图改写能力对比。 物化视图在 StarRocks 中扮演着至关重要的角色&#xff0c;它是进行数据建模和加速…

挑战一个月基本掌握C++(第五天)了解运算符,循环,判断

一 运算符 运算符是一种告诉编译器执行特定的数学或逻辑操作的符号。C 内置了丰富的运算符&#xff0c;并提供了以下类型的运算符&#xff1a; 算术运算符关系运算符逻辑运算符位运算符赋值运算符杂项运算符 1.1 算术运算符 假设变量 A 的值为 10&#xff0c;变量 B 的值为…

通过阿里云 Milvus 与 PAI 搭建高效的检索增强对话系统

背景介绍 阿里云向量检索服务Milvus版&#xff08;简称阿里云Milvus&#xff09;是一款云上全托管服务&#xff0c;确保了了与开源Milvus的100%兼容性&#xff0c;并支持无缝迁移。在开源版本的基础上增强了可扩展性&#xff0c;能提供大规模 AI 向量数据的相似性检索服务。相…

jQuery总结(思维导图+二维表+问题)

关于什么是jQuery&#xff1a;&#xff08;下面是菜鸟里的介绍&#xff09; jQuery 是一个 JavaScript 库。 jQuery 极大地简化了 JavaScript 编程。 jQuery 很容易学习。 而jQuery对我的感受就是&#xff0c;链式运用的很形象&#xff0c;隐式迭代还有一些兼容性强的优点&…

(14)CT137A- 动态数码管设计

&#xff08;1&#xff09;了解板卡原理图中数码管的特性&#xff1a;共阳极数码管&#xff0c;公共端连接了电源&#xff0c;FPGA IO口低电平有效&#xff0c;另外&#xff0c;可以看到位选端FPGA位选低电平时选通。 &#xff08;2&#xff09;刷新时间的设定&#xff1a;众所…

UG NX二次开发(C#)-如何设置UGOpen的UF_CAM_geom_type_e枚举类型

文章目录 1、前言2、UF_CAM_geom_type_e类型说明3、在C#的类型1、前言 UG NX二次开发提供的帮助文档,无论是c/c++、C#、Python、VB语言,其采用UFun函数时都是参考的UGOpen的帮助文档,即是:UGOPEN函数参考手册(nx6).CHM,后续的都没有怎么增加,这个在哪个版本中都适用。但…

内容与资讯API优质清单

作为开发者&#xff0c;拥有一套API合集是必不可少的。这个开发者必备的API合集汇集了各种实用的API资源&#xff0c;为你的开发工作提供了强大的支持&#xff01;无论你是在构建网站、开发应用还是进行数据分析&#xff0c;这个合集都能满足你的需求。你可以通过这些免费API获…

vue使用v-if和:class完成条件渲染

1.使用v-if 和v-else 完成主body和暂无数据两个<tbody>标签的条件渲染(注意与v-show效果的区别) 2.v-for完成列表渲染 3.:class完成分数标红的条件控制 删哪个就传哪个的id&#xff0c;基于这个id去过滤掉相同id的项&#xff0c;把剩下的项返回 <td><a click.p…

C++ OpenGL学习笔记(2、绘制橙色三角形绘制、绿色随时间变化的三角形绘制)

相关文章链接 C OpenGL学习笔记&#xff08;1、Hello World空窗口程序&#xff09; 目录 绘制橙色三角形绘制1、主要修改内容有&#xff1a;1.1、在主程序的基础上增加如下3个函数1.2、另外在主程序外面新增3个全局变量1.3、编写两个shader程序文件 2、initModel()函数3、initS…

vue基础作业实验十

vue基础作业实验十 实验要求案例要点&#xff1a;代码以及思考style部分Vue.js 部分Vue 实例部分 这段代码是一个基于 Vue.js 的静态页面&#xff0c;功能包括商品品牌的添加、删除和搜索。 实验要求 一、实验的基本内容 &#xff08;1&#xff09;Vue模板语法。 &#xff08…