40分钟学 Go 语言高并发:分布式锁实现

分布式锁实现

一、概述

分布式锁是分布式系统中的一个重要组件,用于协调分布式环境下的资源访问和并发控制。我们将从锁设计、死锁预防、性能优化和容错处理四个维度深入学习。

学习目标

维度重点内容掌握程度
锁设计基于Redis/etcd的锁实现原理必须掌握
死锁预防超时机制、重入机制必须掌握
性能优化锁粒度控制、读写分离重点掌握
容错处理节点故障、网络分区重点掌握

二、实现流程图

在这里插入图片描述

三、基础锁实现

让我们首先实现一个基于Redis的分布式锁基础版本:

package distlock

import (
    "context"
    "crypto/rand"
    "encoding/base64"
    "errors"
    "time"

    "github.com/go-redis/redis/v8"
)

type DistributedLock struct {
    client     *redis.Client
    key        string
    value      string
    expiration time.Duration
}

// NewDistributedLock 创建一个新的分布式锁实例
func NewDistributedLock(client *redis.Client, key string, expiration time.Duration) (*DistributedLock, error) {
    // 生成随机值作为锁的标识
    b := make([]byte, 16)
    _, err := rand.Read(b)
    if err != nil {
        return nil, err
    }
    value := base64.StdEncoding.EncodeToString(b)

    return &DistributedLock{
        client:     client,
        key:        key,
        value:      value,
        expiration: expiration,
    }, nil
}

// TryLock 尝试获取锁
func (dl *DistributedLock) TryLock(ctx context.Context) (bool, error) {
    return dl.client.SetNX(ctx, dl.key, dl.value, dl.expiration).Result()
}

// Unlock 释放锁
func (dl *DistributedLock) Unlock(ctx context.Context) error {
    script := `
        if redis.call("get", KEYS[1]) == ARGV[1] then
            return redis.call("del", KEYS[1])
        else
            return 0
        end
    `
    result, err := dl.client.Eval(ctx, script, []string{dl.key}, dl.value).Result()
    if err != nil {
        return err
    }
    if result == 0 {
        return errors.New("lock not held")
    }
    return nil
}

// RefreshLock 刷新锁的过期时间
func (dl *DistributedLock) RefreshLock(ctx context.Context) error {
    script := `
        if redis.call("get", KEYS[1]) == ARGV[1] then
            return redis.call("pexpire", KEYS[1], ARGV[2])
        else
            return 0
        end
    `
    result, err := dl.client.Eval(
        ctx,
        script,
        []string{dl.key},
        dl.value,
        dl.expiration.Milliseconds(),
    ).Result()
    
    if err != nil {
        return err
    }
    if result == 0 {
        return errors.New("lock not held")
    }
    return nil
}

// IsLocked 检查锁是否被持有
func (dl *DistributedLock) IsLocked(ctx context.Context) (bool, error) {
    exists, err := dl.client.Exists(ctx, dl.key).Result()
    if err != nil {
        return false, err
    }
    return exists == 1, nil
}

四、增强版锁实现(带可重入特性)

下面是一个支持可重入的分布式锁实现:

package distlock

import (
    "context"
    "encoding/json"
    "errors"
    "sync"
    "time"

    "github.com/go-redis/redis/v8"
)

type LockInfo struct {
    Owner     string `json:"owner"`
    Count     int    `json:"count"`
    Timestamp int64  `json:"timestamp"`
}

type ReentrantLock struct {
    client     *redis.Client
    key        string
    owner      string
    expiration time.Duration
    mu         sync.Mutex
}

// NewReentrantLock 创建可重入锁
func NewReentrantLock(client *redis.Client, key string, owner string, expiration time.Duration) *ReentrantLock {
    return &ReentrantLock{
        client:     client,
        key:        key,
        owner:      owner,
        expiration: expiration,
    }
}

// Lock 获取可重入锁
func (rl *ReentrantLock) Lock(ctx context.Context) error {
    rl.mu.Lock()
    defer rl.mu.Unlock()

    script := `
        local lockInfo = redis.call('get', KEYS[1])
        if not lockInfo then
            -- 锁不存在,创建新锁
            redis.call('set', KEYS[1], ARGV[1], 'PX', ARGV[2])
            return 1
        end
        
        local info = cjson.decode(lockInfo)
        if info.owner == ARGV[3] then
            -- 重入锁
            info.count = info.count + 1
            info.timestamp = tonumber(ARGV[4])
            redis.call('set', KEYS[1], cjson.encode(info), 'PX', ARGV[2])
            return 1
        end
        
        return 0
    `

    lockInfo := LockInfo{
        Owner:     rl.owner,
        Count:     1,
        Timestamp: time.Now().UnixNano(),
    }

    lockInfoJSON, err := json.Marshal(lockInfo)
    if err != nil {
        return err
    }

    result, err := rl.client.Eval(
        ctx,
        script,
        []string{rl.key},
        string(lockInfoJSON),
        rl.expiration.Milliseconds(),
        rl.owner,
        time.Now().UnixNano(),
    ).Result()

    if err != nil {
        return err
    }

    if result.(int64) == 0 {
        return errors.New("failed to acquire lock")
    }

    return nil
}

// Unlock 释放可重入锁
func (rl *ReentrantLock) Unlock(ctx context.Context) error {
    rl.mu.Lock()
    defer rl.mu.Unlock()

    script := `
        local lockInfo = redis.call('get', KEYS[1])
        if not lockInfo then
            return 0
        end
        
        local info = cjson.decode(lockInfo)
        if info.owner ~= ARGV[1] then
            return -1
        end
        
        info.count = info.count - 1
        if info.count <= 0 then
            redis.call('del', KEYS[1])
            return 1
        else
            redis.call('set', KEYS[1], cjson.encode(info), 'PX', ARGV[2])
            return 1
        end
    `

    result, err := rl.client.Eval(
        ctx,
        script,
        []string{rl.key},
        rl.owner,
        rl.expiration.Milliseconds(),
    ).Result()

    if err != nil {
        return err
    }

    switch result.(int64) {
    case -1:
        return errors.New("lock held by another owner")
    case 0:
        return errors.New("lock not held")
    default:
        return nil
    }
}

// RefreshLock 刷新锁的过期时间
func (rl *ReentrantLock) RefreshLock(ctx context.Context) error {
    script := `
        local lockInfo = redis.call('get', KEYS[1])
        if not lockInfo then
            return 0
        end
        
        local info = cjson.decode(lockInfo)
        if info.owner ~= ARGV[1] then
            return 0
        end
        
        info.timestamp = tonumber(ARGV[3])
        redis.call('set', KEYS[1], cjson.encode(info), 'PX', ARGV[2])
        return 1
    `

    result, err := rl.client.Eval(
        ctx,
        script,
        []string{rl.key},
        rl.owner,
        rl.expiration.Milliseconds(),
        time.Now().UnixNano(),
    ).Result()

    if err != nil {
        return err
    }

    if result.(int64) == 0 {
        return errors.New("lock not held")
    }

    return nil
}

五、死锁预防机制

1. 超时机制

  • 所有锁操作都设置了过期时间
  • 使用看门狗机制自动续期
  • 防止客户端崩溃导致的死锁

2. 死锁检测

检测项处理方式实现难度
循环等待资源有序分配中等
持有等待一次性申请所有资源简单
不可剥夺超时自动释放简单
互斥访问读写分离较难

六、性能优化策略

1. 锁粒度优化

  • 降低锁粒度,提高并发度
  • 使用多粒度锁机制
  • 实现分段锁

2. 读写分离

package distlock

import (
    "context"
    "fmt"
    "time"

    "github.com/go-redis/redis/v8"
)

type RWLock struct {
    client     *redis.Client
    key        string
    owner      string
    expiration time.Duration
}

func NewRWLock(client *redis.Client, key string, owner string, expiration time.Duration) *RWLock {
    return &RWLock{
        client:     client,
        key:        key,
        owner:      owner,
        expiration: expiration,
    }
}

// RLock 获取读锁
func (rwl *RWLock) RLock(ctx context.Context) error {
    script := `
        -- 检查是否存在写锁
        if redis.call('exists', KEYS[1] .. ':write') == 1 then
            return 0
        end
        
        -- 增加读锁计数
        local count = redis.call('incr', KEYS[1] .. ':read')
        redis.call('pexpire', KEYS[1] .. ':read', ARGV[1])
        
        -- 记录读锁持有者
        redis.call('hset', KEYS[1] .. ':readers', ARGV[2], '1')
        redis.call('pexpire', KEYS[1] .. ':readers', ARGV[1])
        
        return 1
    `

    result, err := rwl.client.Eval(
        ctx,
        script,
        []string{rwl.key},
        rwl.expiration.Milliseconds(),
        rwl.owner,
    ).Result()

    if err != nil {
        return fmt.Errorf("failed to acquire read lock: %v", err)
    }

    if result.(int64) == 0 {
        return fmt.Errorf("write lock exists")
    }

    return nil
}

// RUnlock 释放读锁
func (rwl *RWLock) RUnlock(ctx context.Context) error {
    script := `
        -- 检查读锁是否存在
        if redis.call('exists', KEYS[1] .. ':read') == 0 then
            return 0
        end
        
        -- 检查当前客户端是否持有读锁
        if redis.call('hexists', KEYS[1] .. ':readers', ARGV[1]) == 0 then
            return -1
        end
        
        -- 移除读锁持有者记录
        redis.call('hdel', KEYS[1] .. ':readers', ARGV[1])
        
        -- 减少读锁计数
        local count = redis.call('decr', KEYS[1] .. ':read')
        if count <= 0 then
            redis.call('del', KEYS[1] .. ':read')
            redis.call('del', KEYS[1] .. ':readers')
        end
        
        return 1
    `

    result, err := rwl.client.Eval(
        ctx,
        script,
        []string{rwl.key},
        rwl.owner,
    ).Result()

    if err != nil {
        return fmt.Errorf("failed to release read lock: %v", err)
    }

    switch result.(int64) {
    case -1:
        return fmt.Errorf("read lock not held by this client")
    case 0:
        return fmt.Errorf("read lock not exists")
    default:
        return nil
    }
}

// Lock 获取写锁
func (rwl *RWLock) Lock(ctx context.Context) error {
    script := `
        -- 检查是否存在读锁或写锁
        if redis.call('exists', KEYS[1] .. ':read') == 1 or
           redis.call('exists', KEYS[1] .. ':write') == 1 then
            return 0
        end
        
        -- 设置写锁
        redis.call('set', KEYS[1] .. ':write', ARGV[1], 'PX', ARGV[2])
        return 1
    `

    result, err := rwl.client.Eval(
        ctx,
        script,
        []string{rwl.key},
        rwl.owner,
        rwl.expiration.Milliseconds(),
    ).Result()

    if err != nil {
        return fmt.Errorf("failed to acquire write lock: %v", err)
    }

    if result.(int64) == 0 {
        return fmt.Errorf("lock exists")
    }

    return nil
}

// Unlock 释放写锁
func (rwl *RWLock) Unlock(ctx context.Context) error {
    script := `
        -- 检查写锁是否存在且属于当前客户端
        local value = redis.call('get', KEYS[1] .. ':write')
        if not value then
            return 0
        end
        if value ~= ARGV[1] then
            return -1
        end
        
        -- 删除写锁
        redis.call('del', KEYS[1] .. ':write')
        return 1
    `

    result, err := rwl.client.Eval(
        ctx,
        script,
        []string{rwl.key},
        rwl.owner,
    ).Result()

    if err != nil {
        return fmt.Errorf("failed to release write lock: %v", err)
    }

    switch result.(int64) {
    case -1:
        return fmt.Errorf("write lock not held by this client")
    case 0:
        return fmt.Errorf("write lock not exists")
    default:
        return nil
    }
}

七、容错处理

1. 容错机制设计

在这里插入图片描述

2. 故障处理实现

package distlock

import (
    "context"
    "errors"
    "sync"
    "time"

    "github.com/go-redis/redis/v8"
)

type FaultTolerantLock struct {
    master     *redis.Client
    slaves     []*redis.Client
    localLock  sync.Mutex
    key        string
    owner      string
    expiration time.Duration
}

func NewFaultTolerantLock(
    master *redis.Client,
    slaves []*redis.Client,
    key string,
    owner string,
    expiration time.Duration,
) *FaultTolerantLock {
    return &FaultTolerantLock{
        master:     master,
        slaves:     slaves,
        key:        key,
        owner:      owner,
        expiration: expiration,
    }
}

// Lock 获取容错锁
func (ftl *FaultTolerantLock) Lock(ctx context.Context) error {
    // 1. 尝试在主节点获取锁
    if err := ftl.tryLockOnMaster(ctx); err == nil {
        return nil
    }

    // 2. 主节点失败,尝试在从节点获取锁
    if err := ftl.tryLockOnSlaves(ctx); err == nil {
        return nil
    }

    // 3. 所有Redis节点都失败,降级使用本地锁
    ftl.localLock.Lock()
    
    // 4. 启动后台协程尝试恢复到Redis锁
    go ftl.tryRecoverToRedis(context.Background())
    
    return nil
}

func (ftl *FaultTolerantLock) tryLockOnMaster(ctx context.Context) error {
    script := `
        if redis.call('exists', KEYS[1]) == 0 then
            redis.call('set', KEYS[1], ARGV[1], 'PX', ARGV[2])
            return 1
        end
        return 0
    `

    result, err := ftl.master.Eval(
        ctx,
        script,
        []string{ftl.key},
        ftl.owner,
        ftl.expiration.Milliseconds(),
    ).Result()

    if err != nil {
        return err
    }

    if result.(int64) == 0 {
        return errors.New("lock exists")
    }

    return nil
}

func (ftl *FaultTolerantLock) tryLockOnSlaves(ctx context.Context) error {
    // 需要在多数从节点上获取锁才算成功
    successCount := 0
    majorityCount := (len(ftl.slaves) / 2) + 1

    for _, slave := range ftl.slaves {
        if err := ftl.tryLockOnNode(ctx, slave); err == nil {
            successCount++
            if successCount >= majorityCount {
                return nil
            }
        }
    }

    return errors.New("failed to acquire lock on majority of slaves")
}

func (ftl *FaultTolerantLock) tryLockOnNode(ctx context.Context, node *redis.Client) error {
    script := `
        if redis.call('exists', KEYS[1]) == 0 then
            redis.call('set', KEYS[1], ARGV[1], 'PX', ARGV[2])
            return 1
        end
        return 0
    `

    result, err := node.Eval(
        ctx,
        script,
        []string{ftl.key},
        ftl.owner,
        ftl.expiration.Milliseconds(),
    ).Result()

    if err != nil {
        return err
    }

    if result.(int64) == 0 {
        return errors.New("lock exists")
    }

    return nil
}

func (ftl *FaultTolerantLock) tryRecoverToRedis(ctx context.Context) {
    ticker := time.NewTicker(time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-ctx.Done():
            return
        case <-ticker.C:
            // 尝试恢复到Redis主节点
            if err := ftl.tryLockOnMaster(ctx); err == nil {
                ftl.localLock.Unlock()
                return
            }

            // 尝试恢复到Redis从节点
            if err := ftl.tryLockOnSlaves(ctx); err == nil {
                ftl.localLock.Unlock()
                return
            }
        }
    }
}

// Unlock 释放锁
func (ftl *FaultTolerantLock) Unlock(ctx context.Context) error {
    // 尝试释放Redis锁
    if err := ftl.unlockRedis(ctx); err == nil {
        return nil
    }

    // Redis释放失败,释放本地锁
    ftl.localLock.Unlock()
    return nil
}

func (ftl *FaultTolerantLock) unlockRedis(ctx context.Context) error {
    script := `
        if redis.call('get', KEYS[1]) == ARGV[1] then
            return redis.call('del', KEYS[1])
        end
        return 0
    `

    // 先尝试在主节点释放
    result, err := ftl.master.Eval(
        ctx,
        script,
        []string{ftl.key},
        ftl.owner,
    ).Result()

    if err == nil && result.(int64) == 1 {
        return nil
    }

    // 主节点释放失败,尝试在从节点释放
    for _, slave := range ftl.slaves {
        result, err = slave.Eval(
            ctx,
            script,
            []string{ftl.key},
            ftl.owner,
        ).Result()

        if err == nil && result.(int64) == 1 {
            return nil
        }
    }

    return errors.New("failed to release lock on all nodes")
}

八、性能测试与监控

1. 性能指标

指标说明目标值
获取锁延迟从发起请求到获取锁的时间<50ms
释放锁延迟从发起释放到完成的时间<30ms
锁冲突率获取锁失败的比例<10%
QPS每秒处理的锁请求数>1000

2. 监控指标

  1. 系统监控

    • CPU使用率
    • 内存使用
    • 网络延迟
    • 磁盘IO
  2. 业务监控

    • 锁获取成功率
    • 锁超时次数
    • 死锁检测次数
    • 降级次数

九、最佳实践总结

  1. 锁设计

    • 使用唯一标识确保锁的归属
    • 合理设置超时时间
    • 实现可重入机制
    • 使用Lua脚本保证原子性
  2. 死锁预防

    • 实现超时自动释放
    • 避免循环等待
    • 实现锁的重入
    • 定期检测死锁
  3. 性能优化

    • 使用读写锁分离
    • 控制锁粒度
    • 批量处理
    • 使用本地缓存
  4. 容错处理

    • 实现主从切换
    • 支持优雅降级
    • 异步恢复机制
    • 多副本数据同步

怎么样今天的内容还满意吗?再次感谢观众老爷的观看,关注GZH:凡人的AI工具箱,回复666,送您价值199的AI大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!

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

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

相关文章

linux 安装composer

下载composer curl -sS https://getcomposer.org/installer | php下载后设置环境变量&#xff0c;直接通过命令composer -v mv composer.phar /usr/local/bin/composer查看版本看是否安装成功 composer -v

Apache Echarts和POI

目录 Apache ECharts 介绍 入门 绘制一个简单的图表 Apache POI 介绍 通过POI创建Excel文件并且写入文件内容 通过POI读取Excel文件中的内容 导出Excel表格 Apache ECharts 介绍 Apache ECharts 是一款基于 Javascript 的数据可视化图表库&#xff0c;提供直观&#xf…

Linux网络基础知识————网络编程

计算机网络的体系结构 网络采用分而治之的方法设计&#xff0c;将网络的功能划分为不同的模块&#xff0c;以分层的形式有机结合在一起 每层实现不同的功能&#xff0c;其内部实现的方法对外部其他层次来说是透明的&#xff0c;每层向上一层提供服务&#xff0c;使用下一层提供…

微信小程序跳转其他小程序以及跳转网站

一、跳转其他小程序 1.1 知道appid和页面路径 wx.navigateToMiniProgram({appId: appid, // 替换为目标小程序 AppIDpath: pathWithParams, // 小程序路径envVersion: release, // 开发版、体验版或正式版success(res) {console.log("跳转到其他小程序成功&#xff01;&q…

3D 生成重建030-SV3D合成环绕视频以生成3D

3D 生成重建030-SV3D合成环绕视频以生成3D 文章目录 0 论文工作1 论文方法2 实验结果 0 论文工作 论文提出了Stable Video 3D (SV3D)——一个用于生成围绕三维物体的高分辨率图像到多视角视频的潜在视频扩散模型。最近关于三维生成的文献提出了将二维生成模型应用于新视图合成…

AR眼镜_消费级工业AR智能眼镜主板硬件解决方案

AR眼镜的研发是一项复杂的软硬件集成工程&#xff0c;它需要在摄影、音频、交互和连接等多个方面提供卓越的基础体验&#xff0c;因此产品的每个细节都显得尤为重要。 在设计AR眼镜时&#xff0c;重量、体积和散热性能都是必须认真考量的关键因素。在芯片平台的选择上&#xff…

类和对象一

目录 1.类的引入 2.类的定义 3.访问限定符 4.类的作用域 5.类对象模型 6.类的大小 1.类的引入 C语言结构体中只能定义变量&#xff0c;在C中&#xff0c;结构体不仅可以定义变量&#xff0c;也可以定义函数。 C兼容C语言&#xff0c;结构用法可以继续使用 同时sruct也升…

AcWing 906. 区间分组

文章目录 前言代码思路 前言 前面两个都是右端点排序&#xff0c;这个我又是无脑右端点排序&#xff0c;直接 wa 了。哭。感觉反正做什么事情都不要太着急&#xff0c;心平气和地做还是比较好。没什么大不了的。考点统计 代码 #include<bits/stdc.h> using namespace …

用拉普拉斯变换的方差算法实现相机自动对焦

使用拉普拉斯变换的方差来计算图像的清晰度的主要原因是拉普拉斯算子可以有效检测图像的边缘和高频细节。图像的清晰度与边缘强度和高频分量的丰富程度密切相关,以下是更详细的解释: 1. 拉普拉斯算子的作用 拉普拉斯算子是一种二阶导数算子,定义为: 它可以在图像中检测快…

非文件形式的内存动态函数库调用接口

使用memfd的系统调用接口将动态库加载到proc虚拟文件系统&#xff0c;提供的fd为进程持有的句柄&#xff0c;通过dlopen的path指向此句柄&#xff0c;即可实现非文件系统加载动态链接库。 文章目录 一、memfd_create二、dl_open三、示例参考 一、memfd_create 接口名称int mem…

SpringBoot 开源停车场管理收费系统

一、下载项目文件 下载源码项目文件口令&#xff1a; 【前端小程序地址】(3.0)&#xff1a;伏脂火器白泽知洞座/~6f8d356LNL~:/【后台管理地址】(3.0)&#xff1a;伏脂火器仇恨篆洞座/~0f4a356Ks2~:/【岗亭端地址】(3.0)&#xff1a;动作火器智汇堂多好/~dd69356K6r~:/复制口令…

计算生成报价单小程序系统开发方案

计算生成报价单小程序报价系统&#xff0c;是根据商品品牌、类型、型号、规格、芯数、特性、颜色、分类进行选择不同的参数进行生成报价单&#xff0c;要求报价单支持生成图片、pdf、excel表格。 计算生成报价单小程序系统的主要功能模块有&#xff1a; 1、在线生成报价单&…

当 webclient 返回复杂json, 但是我只需要其中几个字段的解决方案

当 webclient 返回复杂json, 但是我只需要其中几个字段的解决方案: Spring 的 WebClient 使用 Jackson 作为默认的 JSON 序列化和反序列化工具&#xff0c;可以轻松将 JSON 映射为对象。

【C/C++】指针相关题目(个人笔记)

我们来详细分析这个C程序的执行流程&#xff0c;并预测它的输出结果。 首先&#xff0c;看一下程序的代码&#xff1a; #include <stdio.h>void main() {int a {1, 2, 3, 4};int *p;p &a;printf("%d ", *p);printf("%d\n", *--p); } 接下来&a…

在算网云平台云端在线部署stable diffusion (0基础小白超详细教程)

Stable Diffusion无疑是AIGC领域中的AI绘画利器&#xff0c;具有以下显著优势&#xff1a; 1、开源性质&#xff0c;支持本地部署 2、能够实现对图像生成过程的精确控制 虽然SD在使用上有很多的有点&#xff0c;但缺点也是不言而喻的&#xff0c;由于AI绘画的整个过程以及现…

初次使用uniapp编译到微信小程序编辑器页面空白,真机预览有内容

uniapp微信小程序页面结构 首页页面代码 微信小程序模拟器 模拟器页面为空白时查了下&#xff0c;有几个说是“Hbuilder编译的时候应该编译出来一个app.js文件 但是却编译出了App.js”&#xff0c;但是我的小程序结构没问题&#xff0c;并且真机预览没有问题 真机调试 根据defi…

【工业机器视觉】基于深度学习的仪表盘识读(读数识别)(1)

前言 本文旨在详述机器视觉技术在水表自动化读数领域的应用&#xff0c;具体聚焦于通过深度学习与传统图像处理方法相结合的方式&#xff0c;实现对仪表盘上字轮数字及指针位置的精准识别。在此基础上&#xff0c;通过对指针角度的分析进行初次读数校正&#xff0c;并利…

C语言数据结构作业

一、在堆区空间连续申请5个int类型大小空间&#xff0c;用来存放从终端输入的5个学生成绩&#xff0c;然后显示5个学生成绩。再将学生成绩升序排序&#xff0c;排序后&#xff0c;再次显示学生成绩。显示和排序分别用函数完成。 要求&#xff1a;用malloc和free完成。 二、课程…

C—指针初阶(2)

如果看完阁下满意的话&#xff0c;能否一键三连呢&#xff0c;我的动力就是大家的支持与肯定&#xff0c;冲&#xff01; 二级指针 我们先看概念以及作用&#xff1a;用来存放一级指针的地址的指针 先看例子&#xff0c;我们逐一分析 我们先分析上面那个“1” 标注那里&#x…

成立北京高途公益基金会,陈向东用爱点亮教育公益新征程

12月10日&#xff0c;北京高途公益基金会正式成立。本次成立仪式在京举办&#xff0c;以“用爱点亮”为主题&#xff0c;吸引了来自教育、公益慈善、媒体等领域的200多名嘉宾参加。 活动中&#xff0c;北京高途公益基金会与北京师范大学教育基金会签署了战略合作协议&#xff…