go|context源码解析

article/2025/6/11 13:58:52

文章目录

  • Context接口
    • Deadline()
    • Done()
    • Err()
    • Value()
  • canceler接口
  • ctx
    • emptyCtx
    • cancelCtx
    • timerCtx
    • valueCtx
  • 基本使用
    • cancelCtx
    • valueCtx

首先看一下源码对“context”的描述,

When a Context is canceled, all Contexts derived from it are also canceled.

当一个Context被取消时,所有从它派生的Context也会被取消。

The WithCancel, WithDeadline, and WithTimeout functions take a Context (the parent) and return a derived Context (the child) and a CancelFunc. Calling the CancelFunc cancels the child and its children, removes the parent’s reference to the child, and stops any associated timers. Failing to call the CancelFunc leaks the child and its children until the parent is canceled or the timer fires. The go vet tool checks that CancelFuncs are used on all control-flow paths.

WithCancel、WithDeadline和WithTimeout函数接受一个Context(父类)并返回一个派生Context(子类)和一个CancelFunc。调用CancelFunc会取消子context及其由该context派生出的子context,移除父context对子context的引用,并停止所有相关的计时器。未能调用CancelFunc会泄漏子context及其由它派生出的子context,直到父context被取消或计时器触发。go - vet工具检查在所有控制流路径上是否使用了CancelFuncs。

Programs that use Contexts should follow these rules to keep interfaces consistent across packages and enable static analysis tools to check context propagation:

使用上下文的程序应该遵循这些规则,以保持包之间的接口一致,并使静态分析工具能够检查上下文传播:

Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it. The Context should be the first parameter, typically named ctx:
func DoSomething(ctx context. Context, arg Arg) error {
use ctx
}

不要在结构类型中存储上下文;相反,将上下文显式地传递给需要它的每个函数。Context应该是第一个参数,通常命名为ctx:
func DoSomething(ctx context. Context, arg Arg) error {
use ctx
}

Do not pass a nil Context, even if a function permits it. Pass context.TODO if you are unsure about which Context to use. The same Context may be passed to functions running in different goroutines; Contexts are safe for simultaneous use by multiple goroutines.Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions.

即使函数允许也不要传递一个nil_context,如果不知道使用哪一个context可以传递context.todo[其实就是emptyctx]。
相同的context可以被传递在运行在不同协程中的多个函数上.多个goroutine使用同一个context是并发安全的。
context值应该用于在函数和API之间共享的请求范围数据,比如身份验证令牌、请求ID或跟踪信息等数据。,而不是用于传递可选参数。可选参数应该明确地定义为函数参数,以确保代码的清晰性和可维护性。

接下来分析源码

Context接口

type Context interface {Deadline() (deadline time.Time, ok bool)Done() <-chan struct{}Err() errorValue(key any) any
}

可以看出"Context"接口包含了四个方法,接下来依次介绍这四个方法。

Deadline()

Deadline() (deadline time.Time, ok bool)

Deadline()返回当前context应该被取消的时间,如果没有设置取消时间,ok为false。

在这里插入图片描述
运行结果如下

0001-01-01 00:00:00 +0000 UTC false
0001-01-01 00:00:00 +0000 UTC false
2023-11-06 15:44:34.580231 +0800 CST m=+5.003172901 true

Done()

Done() <-chan struct{}

Done返回一个只读chan,由于只读所以只有该通道关闭时才会唤醒所有监听该通道的协程,至于何时关闭,有可能时超时关闭,也有可能通过cancelfunc手动关闭。如果无法被关闭返回回一个nil值。对Done()的连续调用会返回相同的值。
Done被用于select语句

func Stream(ctx context.Context, out chan<- Value) error {for {v, err := DoSomething(ctx)if err != nil {return err}select {case <-ctx.Done():return ctx.Err()case out <- v:}}}

Err()

Err() error
  • 通道没有被关闭,返回nil
  • 通道被关闭返回关闭的原因
    如果context被取消返回"Canceled"
    如果超时取消会返回"DeadlineExceeded"
//context被cancelfunc取消返回的err
var Canceled = errors.New("context canceled")//context因超时被取消
var DeadlineExceeded error = deadlineExceededError{}type deadlineExceededError struct{}func (deadlineExceededError) Error() string   { return "context deadline exceeded" }func (deadlineExceededError) Timeout() bool   { return true }func (deadlineExceededError) Temporary() bool { return true }

Value()

Value(key any) any

Value返回context中与key相关联的value值,没有与之关联的value值返回nil,连读的调用返回相同的结果。
键标识context中的特定值。希望在Context中存储值的函数通常在全局变量中分配一个键,然后将该键用作Context的参数。

由于需要使用"key"进行比较所以需要key是可比较的任何类型。并且建议将键定位为未导出的变量,这样可以避免产生冲突。

定义Context键的包应该为使用该键存储的值提供类型安全的访问器:package user定义了存储在context类型中的value值

package user
import "context"
type User struct {...}
type key int    
//未导出的key值,避免冲突;user使用userkey;uservalue存储在context中
var userKey key
//clients use user.NewContext and user.FromContext instead of using this key directly.//NewContext 返回一个携带u值的context
func NewContext(ctx context.Context, u *User) context.Context {return context. WithValue(ctx, userKey, u)
}//FromContext 根据userkey返回value值
func FromContext(ctx context.Context) (*User, bool) {u, ok := ctx.Value(userKey).(*User)return u, ok
}

接下来看看"Context"的具体实现

canceler接口

//canceler是一个可以被直接取消的context 类型
//具体实现是*cancelCtx 和 *timerCtx
type canceler interface {cancel(removeFromParent bool, err, cause error)Done() <-chan struct{}
}

ctx

emptyCtx

/*
emptyCtx永远不会被取消,没有值,也没有截止日期。它不是struct{},因为这种类型的变量必须有不同的地址。[就像C++中的empty class的大小并不是0,因为为了保证类对象的每个实例都有唯一的地址,会在empty class中插入一个变量]
*/
type emptyCtx int

接下来看看empty如何实现Context接口的四种方法

//由于emptyCtx没有设置取消时间故直接返回nil和false
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {return
}
//emptyCtx不能被取消
func (*emptyCtx) Done() <-chan struct{} {return nil
}func (*emptyCtx) Err() error {return nil
}
//emptyCtx不能存储值
func (*emptyCtx) Value(key any) any {return nil
}

emptyCtx有两个具体的变量

var (background = new(emptyCtx)todo       = new(emptyCtx)
)
func (e *emptyCtx) String() string {switch e {case background:return "context.Background"case todo:return "context.TODO"}return "unknown empty Context"
}

/*
Background返回一个非空的empty Context。它永远不会被取消,没有值,也没有截止日期。它通常由main函数、初始化和测试使用,并作为传入请求的根context。
*/
func Background() Context {return background
}
/*
TODO返回一个非空的empty Context。当不清楚使用哪个context或context还不可用时(因为周围的函数还没有扩展到接受上下文参数)使用todo
*/
func TODO() Context {return todo
}

cancelCtx

首先看一下cancelCtx的结构定义

//cancelCtx可以被取消。当被取消的时候,也会取消实现了
//canceler接口的chidlen context
type cancelCtx struct {Context //组合继承Context接口mu       sync.Mutex            // protects following fieldsdone     atomic.Value          // of chan struct{}, created lazily, closed by first cancel call 用来懒汉式创造chan,被关闭通过第一次的cancel调用children map[canceler]struct{} // set to nil by the first cancel callerr      error                 // set to non-nil by the first cancel callcause    error                 // set to non-nil by the first cancel call
}

看一下cancelFunc的定义

//CancelFun不会等work停止,会强制终止当前的work
//可以被多个写成同时调用
//在第一次调用之后,之后的调用CancelFunc什么也不做
type CancelFunc func()

看一下如何获取cancelCtx

/*
WithCancel返回一个副本:父context和一个新的Done通道。
返回的context通道被关闭的时机:
1.调用返回的cancelfunc
2.父context的Done chan被关闭。
取消context会释放它所关联的资源,所以运行在该context的操作完成之后应该调用cancelfunc释放资源
*/
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {c := withCancel(parent)return c, func() { c.cancel(true, Canceled, nil) }
}
func withCancel(parent Context) *cancelCtx {if parent == nil {// parent context不能为nilpanic("cannot create context from nil parent")}// 将c的Context设置为parentc := newCancelCtx(parent)// 找到parent中的cancelCtx// 将c加入到该cancelCtx的child列表propagateCancel(parent, c)return c
}
// newCancelCtx returns an initialized cancelCtx.
func newCancelCtx(parent Context) *cancelCtx {return &cancelCtx{Context: parent}
}
//从这里也可以看出WithCancel(parent ...)返回的是一个带有parent context和new done chan的副本。

接下来看一下withcancel的另外一个函数“propagateCancel”

[propagate传播]
// propagateCancel arranges for child to be canceled when parent is.
//这个函数的作用是将child节点加入到parent节点的childen中
func propagateCancel(parent Context, child canceler) {//获取parent的Done()done := parent.Done()//父context不能被取消,什么也不做if done == nil {return // parent is never canceled}select {case <-done://父context已经被取消,直接取消子context// parent is already canceledchild.cancel(false, parent.Err(), Cause(parent))returndefault://放行执行下面的逻辑}//在parent中找到了*cancelCtxif p, ok := parentCancelCtx(parent); ok {p.mu.Lock()if p.err != nil {// parent has already been canceledchild.cancel(false, p.err, p.cause)} else {if p.children == nil {// 懒惰式创建p.children = make(map[canceler]struct{})}//将child加入到cancelCtx的children中p.children[child] = struct{}{}}p.mu.Unlock()} else {/*// goroutines counts the number of goroutines ever created; 记录创建的协程数量var goroutines atomic.Int32*/goroutines.Add(1)//新开启一个协程监听parent.Done()和child.Done()go func() {select {case <-parent.Done():child.cancel(false, parent.Err(), Cause(parent))case <-child.Done():}}()}
}
/*
parentCancelCtx返回父对象的底层*cancelCtx。它通过查找
parent.Value(&cancelCtxKey)来找到最里面的封闭
*cancelCtx,然后检查parent.Done()是否与*cancelCtx匹配。
(如果没有,*cancelCtx已经被封装在提供不同done通道的自定义实
现中,在这种情况下我们不应该绕过它。)
*/
//&cancelCtxKey is the key that a cancelCtx returns itself for.
var cancelCtxKey int
func parentCancelCtx(parent Context) (*cancelCtx, bool) {//获取parent的contextdone := parent.Done()/*closedchan是一个可重用的已经被关闭的通道var closedchan = make(chan struct{})func init() {close(closedchan)}*/if done == closedchan || done == nil {return nil, false}//在parent中找到cancelCtxKey对应的value值p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)if !ok {//没有找到return nil, false}//加载从父context中找到的cancelCtx的chanpdone, _ := p.done.Load().(chan struct{})//从父context中寻找到的cancelCtx的Done()chan与从parent中的Done()获取到的chan不是同一个,说明parent自定义了Done()方法if pdone != done {return nil, false}return p, true
}

看一下cancelCtx对Context接口的实现
cancelCtx并没有实现Deadline方法,所以是继承了parent ctx的Deadline()方法

func (c *cancelCtx) Value(key any) any {if key == &cancelCtxKey {return c}return value(c.Context, key)
}
func value(c Context, key any) any {for {//c一定要是一个interface才可以使用c.(type)switch ctx := c.(type) {case *valueCtx:// valueCtx返回对应的val值if key == ctx.key {return ctx.val}c = ctx.Contextcase *cancelCtx:// cancelCtx返回自身if key == &cancelCtxKey {return c}c = ctx.Contextcase *timerCtx:// timerCtx返回其中的cancelCtxif key == &cancelCtxKey {return ctx.cancelCtx}c = ctx.Contextcase *emptyCtx:return nildefault://自定义的Value方法return c.Value(key)}}
}
//双重检测创建chan
func (c *cancelCtx) Done() <-chan struct{} {//c.done是一个atomic.Value类型的变量,是对结构体类型的原子操作,是并发安全的//获取c中存储的chan struct{}d := c.done.Load()if d != nil {//获取到直接返回return d.(chan struct{})}//获取不到加锁构建c.mu.Lock()defer c.mu.Unlock()d = c.done.Load()//双重检测if d == nil {//新建d = make(chan struct{})c.done.Store(d)}return d.(chan struct{})
}
func (c *cancelCtx) Err() error {
//因为是并发安全的,所以要加锁进行保护c.mu.Lock()err := c.errc.mu.Unlock()return err
}
// cancel sets c.cause to cause if this is the first time c is canceled.
//cancel 调用关闭c.done,取消每一个c的children,
//如果removeFromParent是true,将c从parent的children中移除。
func (c *cancelCtx) cancel(removeFromParent bool, err, cause error) {if err == nil {panic("context: internal error: missing cancel error")}if cause == nil {cause = err}c.mu.Lock()if c.err != nil {//c已经被取消过c.mu.Unlock()return // already canceled}//设置c.errc.err = errc.cause = cause//获取c中的chand, _ := c.done.Load().(chan struct{})if d == nil {//closedchan是一个可重用的已经被关闭的通道c.done.Store(closedchan)} else {//closeclose(d)}//遍历c.children关闭由c派生出的cancelCtxfor child := range c.children {// NOTE: acquiring the child's lock while holding parent's lock.// removeFromParent为true,需要获取父节点的锁,但此时已经持有着父节点的锁,所以为falsechild.cancel(false, err, cause)}c.children = nilc.mu.Unlock()//从父context中的children中移除cif removeFromParent {removeChild(c.Context, c)}
}
// removeChild removes a context from its parent.
func removeChild(parent Context, child canceler) {p, ok := parentCancelCtx(parent)if !ok {return}p.mu.Lock()if p.children != nil {//将child从parent的children中移除delete(p.children, child)}p.mu.Unlock()
}

timerCtx

首先看一下timerCtx的定义

/*timerCtx带有一个计时器和一个截止日期。它嵌入了一个			cancelCtx来实现Done和Err。它通过停止计时器,然后委托给cancelCtx.cancel来实现取消。
*/
type timerCtx struct {*cancelCtxtimer *time.Timer // 定时器deadline time.Time //到期时间
}

timerCtx组合继承了cancelCtx,由cancelCtx实现Done()和Err()方法,Deadline()和cancel()方法由timerCtx自己实现

func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {return c.deadline, true
}
func (c *timerCtx) cancel(removeFromParent bool, err, cause error) {c.cancelCtx.cancel(false, err, cause)if removeFromParent {// Remove this timerCtx from its parent cancelCtx's children.removeChild(c.cancelCtx.Context, c)}c.mu.Lock()//关闭定时器if c.timer != nil {c.timer.Stop()c.timer = nil}c.mu.Unlock()

看看获取*timerCtx的方法"withDeadline"

/*withDeadline返回一个带有parent context和截止时间deadline的副本;如果parent context的deadline早于d,WithDeadline(parent,d)等同于WithCancel(parent) ;返回的context chan被关闭的时机:1.到截止时间2.cancelfunc被调用3.parent chan被关闭。取消上下文会释放与之关联的资源,所以执行完相关的操作之后要调用cancelfunc释放资源*/
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {if parent == nil {panic("cannot create context from nil parent")}if cur, ok := parent.Deadline(); ok && cur.Before(d) {// parent的过期时间早于child,等价于cancelCtxreturn WithCancel(parent)}c := &timerCtx{cancelCtx: newCancelCtx(parent),deadline:  d,}// 找到parent中的cancelCtx// 将c加入到该cancelCtx的child列表propagateCancel(parent, c)//当前时间距离d的间隙dur := time.Until(d)if dur <= 0 {//到期取消c.cancel(true, DeadlineExceeded, nil) // deadline has already passedreturn c, func() { c.cancel(false, Canceled, nil) }}//设置定时器c.mu.Lock()defer c.mu.Unlock()if c.err == nil {//到达截止时间dur之后调用自定义的functionc.timer = time.AfterFunc(dur, func() {c.cancel(true, DeadlineExceeded, nil)})}return c, func() { c.cancel(true, Canceled, nil) }
}

另一个创建*timerCtx的方式"withTimeout"

/*
取消context会释放与它相关的资源,所以代码应该
在此context运行的操作完成后立即调用cancel:
func slowOperationWithTimeout(ctx context.Context) (Result, error) {ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)defer cancel()  // releases resources if slowOperation completes before timeout elapsesreturn slowOperation(ctx)
}
*/
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {return WithDeadline(parent, time.Now().Add(timeout))
}

valueCtx

先看一下valueCtx的定义

//valueCtx带有key-value pair,可以存储值但不能被取消;
//只实现自己Value方法,其它的方法由Context 接口实现
type valueCtx struct {Contextkey, val any
}

创建*valueCtx的方法"WithValue"

/*
提供的key必须是可比较的,并且不应该是string或者任何其它的内置类型这样可以在使用context的package之间避免冲突。使用WithValue必须定义自己的key类型。
为了避免将key赋值给interface的时候产生额外的分配,应该将key设置为具体的struct或者具体的内置类型;如果想将key设置为可导出[exported]的变量,那么key的静态数据类型应该是指针或者接口类型的,这样可以避免在上下文传递是引起不必要的拷贝。example:
var MyKey *string
var MyKey interface{}
*/
func WithValue(parent Context, key, val any) Context {if parent == nil {panic("cannot create context from nil parent")}if key == nil {panic("nil key")}//key一定要是可比较的类型//go中不可比较的类型有三个:slice,map,functionif !reflectlite.TypeOf(key).Comparable() {panic("key is not comparable")}return &valueCtx{parent, key, val}
}

*valueCtx实现的Value方法

func (c *valueCtx) Value(key any) any {if c.key == key {return c.val}return value(c.Context, key)
}func value(c Context, key any) any {for {switch ctx := c.(type) {case *valueCtx:if key == ctx.key {return ctx.val}c = ctx.Contextcase *cancelCtx:if key == &cancelCtxKey {return c}c = ctx.Contextcase *timerCtx:if key == &cancelCtxKey {return ctx.cancelCtx}c = ctx.Contextcase *emptyCtx:return nildefault:return c.Value(key)}}
}

以上有关context的源码就分析结束了,看一下各个Ctx的应用

基本使用

cancelCtx

func main() {get := func(ctx context.Context) <-chan int {ch := make(chan int)n := 1go func() {for {select {case <-ctx.Done():returncase ch <- n:n++//default: //不要有这个分支不然这个子goroutine会一直for轮询直到被抢占调度}}}()return ch}ctx, cancel := context.WithCancel(context.Background())defer cancel()//防止goroutine泄露for v := range get(ctx) {fmt.Println(v)if v == 5 {break}}
}

valueCtx

type keytype string
var key keytypefunc main(){//WithValue的example,自定义具体的key类型f := func(ctx context.Context, key keytype) {if v := ctx.Value(key); v != nil {fmt.Println("find key ,value", v)} else {fmt.Println("not find key,", key)}}ctx1 := context.WithValue(context.Background(), key, "GO")f(ctx1, key)//find key ,value GOf(ctx1, keytype("key"))//not find key, key
}

/*
CancelFuncCause的作用等同于CancelFunc,但是添加了额外的取消原因[cancellation cause],如果context已经被取消不会设置cause
*/
// For example, if childContext is derived from parentContext:
//   - if parentContext is canceled with cause1 before childContext is canceled with cause2,
//     then Cause(parentContext) == Cause(childContext) == cause1
//   - if childContext is canceled with cause2 before parentContext is canceled with cause1,
//     then Cause(parentContext) == cause1 and Cause(childContext) == cause2
type CancelCauseFunc func(cause error)/*
WithCancelCause 的行为等同于WithCancel,但是返回的是CancelFuncCause而不是CancelFunc。调用CancelFuncCause并传入一个非nil的error,可以通过Cause(ctx)进行检索。如果调用CancelFuncCause时传入nil,cause会被设置为Canceled与err记录的值一样
*/
func WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc) {c := withCancel(parent)return c, func(cause error) { c.cancel(true, Canceled, cause) }
}

使用例子

ctx, cancel := context.WithCancelCause(parent)
cancel(myError)
ctx.Err() // returns context.Canceled
context.Cause(ctx) // returns myError

http://www.hkcw.cn/article/jmmZEpivVV.shtml

相关文章

香港中乐团六月京津巡演 携多位国际艺术家献演

被誉为“民乐翘楚”的香港中乐团受邀于6月13至18日赴北京及天津举行三场“阎惠昌与香港中乐团2025”内地巡演。乐团荣幸获得中华人民共和国香港特别行政区政府驻北京办事处&#xff0c;及巡演赞助商李锦记国际控股有限公司全力支持&#xff0c;将于北京艺术中心的《笙与管风琴的…

[yolov11改进系列]基于yolov11引入高效上采样卷积块EUCB的python源码+训练源码

【EUCB介绍】 论文介绍 题目 EMCAD:Efficient Multi-scale Convolutional Attention Decoding for Medical Image Segmentation 论文地址 https://arxiv.org/pdf/2405.06880 创新点 多尺度卷积解码器&#xff1a;提出了一种高效的多尺度卷积注意力解码器&#xff08;EMCAD&a…

oscp练习PG Monster靶机复现

端口扫描 nmap -A -p- -T4 -Pn 192.168.134.180 PORT STATE SERVICE VERSION 80/tcp open http Apache httpd 2.4.41 ((Win64) OpenSSL/1.1.1c PHP/7.3.10) |_http-server-header: Apache/2.4.41 (Win64) OpenSSL/1.1.1c PHP/7.3.10 | http-methods:…

樊振东莫雷加德成为队友 共迎新挑战

6月1日,萨尔布吕肯俱乐部宣布樊振东加盟德甲联赛。在萨尔布吕肯乒乓球甲级俱乐部的官宣消息中,樊振东表示他非常期待在萨尔布吕肯和德甲的新挑战,体验新的环境,并与球队一起赢得更多胜利。球队经理透露,这一切来得如此令人惊讶且迅速,他们仍然难以完全相信。但这是现实—…

任务中心示例及浏览器强制高效下载实践

1. 效果展示 这里的进度展示&#xff0c;可以通过我们之前讲到的Vue3实现类ChatGPT聊天式流式输出(vue-sse实现) SSE技术实现&#xff0c;比如用户点击全量下载时&#xff0c;后台需要将PDF文件打包为ZIP文件&#xff0c;由于量较大&#xff0c;需要展示进度&#xff0c;用户点…

危地马拉发现2800多年前玛雅城市遗址

var chan_v_w = 960,chan_v_h = 540,chan_v_p = https://mts-audio.huawangzhixun.com/image/20250601/news/5512b3f2-c97c-4299-b34c-8f5e57413ca6.jpg,chan_v_s = https://vmts.china.com/api/video/onaliyun/query?id=3101125&ttype=mp4;据危地马拉媒体5月30日报道,危…

姜老师MBTI课程:4条轴线的总结

文稿&#xff1a; 第一点是有的时候真的分不清到底是N还是S而且会跟自己的朋友去争论他这个表现到底是J还是P说不清。感觉有些案例姜老师讲的也是既有J又有P最后娶了一个什么&#xff1f;能不能够给我们提一些非常精准的标准&#xff0c;帮我们来做尽量精准的判断。好&#xff…

限流算法相关知识点

目录 固定窗口算法滑动窗口算法漏桶算法令牌桶算法Sentinel限流例子 固定窗口算法 固定窗口算法的实现相对简单。系统维护一个计数器&#xff0c;每当请求到来时&#xff0c;计数器加一。当时间窗口结束时&#xff0c;计数器清零。如果在一个时间窗口内请求数量超过了预设的阈…

第N个泰波那契数列 --- 动态规划

目录 一&#xff1a;题目 二&#xff1a;算法原理 三&#xff1a;代码实现 一&#xff1a;题目 题目链接&#xff1a;1137. 第 N 个泰波那契数 - 力扣&#xff08;LeetCode&#xff09; 二&#xff1a;算法原理 三&#xff1a;代码实现 class Solution { public:int trib…

【题解-洛谷】P3467 [POI 2008] PLA-Postering

题目&#xff1a;P3467 [POI 2008] PLA-Postering 题目描述 Byteburg 城市的东区所有建筑都是按照旧式建筑风格建造的&#xff1a;它们一个接一个地紧挨在一起&#xff0c;中间没有任何间隔。它们从东到西排列&#xff0c;形成了一排高度各异的建筑长廊。 Byteburg 的市长 B…

樊振东加盟 德国俱乐部:他主动的 新挑战迎期待

当樊振东在社交媒体上发布观赛欧冠的照片时,另一条消息也震惊了乒乓球圈。6月1日,德国乒乓球甲级联赛FC萨尔布吕肯乒乓球俱乐部宣布,奥运冠军樊振东将加盟球队。樊振东表示,他非常期待在萨尔布吕肯和德甲的新挑战,体验新的环境,并与球队一起赢得更多胜利。球队体育总监埃…

雷军祝儿童节快乐 分享健身照送祝福

6月1日,小米科技创始人、董事长雷军在微博分享了健身打卡照片,并向小朋友送上儿童节祝福,祝所有小朋友都有一个快乐的童年。他还附上了一张富有童趣的AI图片,图片中“Q版雷军”坐在旋转木马上,双手紧握拳头,呈现出加油打气的动作。责任编辑:zx0176

Vue ①-实例 || 指令

Vue Vue 是一个构建用户界面的渐进式框架&#xff0c;也就是 UI 框架。 创建 Vue 实例 创建 Vue实例&#xff0c;初始化渲染&#xff1a; 准备容器&#xff08;Vue所管理的范围&#xff09;引包&#xff08;开发版本包 / 生产版本包&#xff08;官网找&#xff09;&#xf…

尚硅谷redis7 93-97 springboot整合reids之总体概述

93 springboot整合reids之总体概述 总体概述 jedis-lettuce-RedisTemplate三者的联系 名称类型作用描述和其它的关系JedisRedis 客户端早期主流的 Java Redis 客户端&#xff0c;基于阻塞 I/O&#xff0c;同步操作可作为 RedisTemplate 的底层连接实现LettuceRedis 客户端基…

两个女首富杠起来了 医美巨头争议不断

巨子生物和华熙生物近期陷入了各自的尴尬境地。市值745亿港元的医美新贵巨子生物因一位博主的视频面临了广泛的造假质疑。与此同时,业绩下滑的玻尿酸巨头华熙生物发文痛斥券商研报“踩一捧一”,试图证明玻尿酸还未过时,但显得有些无力。一些曾鼓吹重组胶原蛋白和巨子生物的券…

屈原的老家端午要过三次 三次端午的独特庆祝

端午节作为我国最古老的节日之一,在众多习俗中,以纪念屈原影响最为广泛。屈原出生于战国时期的湖北秭归,当地不仅保留着典型的屈原故里端午习俗,还有“端午比年大”的说法。在屈原的家乡湖北秭归乐平里,这里位于山谷中央,四面群山环抱,不远处是长江的支流香溪河。古籍记…

张若昀一家三口现身伦敦过六一 异国街头温馨同游

6月1日,张若昀和唐艺昕带着女儿在伦敦游玩时被网友偶遇。一家三口走在异国街头,画面十分温馨。张若昀推着婴儿车,尽显父爱;唐艺昕穿着黑色衣服,皮肤白皙气质出众;女儿坐在小车上乖巧可爱。明星们在国内逛街容易受到关注,因此他们更倾向于在国外享受私人时光。这次张若昀…

黄石公园车祸致5华人身亡司机涉酒驾 肇事司机酒精含量超标两倍

美国警方于5月30日透露,5月初在黄石国家公园附近发生的一起导致7人死亡的交通事故中,肇事皮卡司机的血液酒精含量超过法定驾驶限值的两倍。这起事故发生在5月1日晚,地点位于美国爱达荷州黄石国家公园附近。事故造成7人死亡、多人受伤。中国驻旧金山总领馆在5月2日确认,死者…

叶童回应与陈丽君合作 虚实共生演绎许仙

5月31日晚,陈丽君与叶童在央视端午晚会上同框,共唱戏歌《浮生一白》。叶童随后在微博发文表示,这次与陈丽君的合作非常特别,虚实共生,共同演绎了这首歌曲。陈丽君也转发了这条微博,表达了与叶童老师合作的喜悦。叶童曾在经典剧作《新白娘子传奇》中成功塑造许仙一角。此次…

AI时代,大公司如何创新?

大模型时代&#xff0c;以OpenAI&#xff0c;DeepSeek为首的一些创业公司迅速崛起&#xff0c;微软、苹果、谷歌、华为等大公司跟他们相比&#xff0c;明显迟缓。所以自己最近一直在思考和观察创业公司的创新模式&#xff0c;看看有什么启发。 “强大的物种追求繁殖能力强。”是…