40分钟学 Go 语言高并发:【实战】并发安全的配置管理器

【实战】并发安全的配置管理器

一、课程概述

学习要点重要程度掌握目标
配置热更新★★★★★理解配置热更新原理,实现动态加载配置
并发读写控制★★★★★掌握并发安全的读写控制机制
观察者模式★★★★☆理解并实现配置变更通知机制
版本管理★★★★☆实现配置版本控制和回滚功能

二、核心知识详解

2.1 设计目标

  1. 支持配置的并发安全读写
  2. 实现配置的热更新机制
  3. 配置变更时通知订阅者
  4. 支持配置版本管理和回滚
  5. 高性能的读操作支持

让我们通过一个完整的示例来实现这个配置管理器。

package configmanager

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "sync"
    "time"
)

// Config 代表配置内容
type Config struct {
    Version   int               `json:"version"`
    UpdatedAt time.Time        `json:"updated_at"`
    Data      map[string]interface{} `json:"data"`
}

// Observer 定义配置变更的观察者接口
type Observer interface {
    OnConfigChange(newConfig Config)
}

// ConfigManager 配置管理器
type ConfigManager struct {
    mu          sync.RWMutex
    config      Config
    observers   []Observer
    versions    []Config  // 保存历史版本
    maxVersions int       // 最大保存的版本数
}

// NewConfigManager 创建新的配置管理器
func NewConfigManager(maxVersions int) *ConfigManager {
    return &ConfigManager{
        config: Config{
            Version:   0,
            UpdatedAt: time.Now(),
            Data:      make(map[string]interface{}),
        },
        versions:    make([]Config, 0),
        maxVersions: maxVersions,
    }
}

// Subscribe 订阅配置变更
func (cm *ConfigManager) Subscribe(observer Observer) {
    cm.mu.Lock()
    defer cm.mu.Unlock()
    cm.observers = append(cm.observers, observer)
}

// Unsubscribe 取消订阅
func (cm *ConfigManager) Unsubscribe(observer Observer) {
    cm.mu.Lock()
    defer cm.mu.Unlock()
    for i, obs := range cm.observers {
        if obs == observer {
            cm.observers = append(cm.observers[:i], cm.observers[i+1:]...)
            break
        }
    }
}

// LoadFromFile 从文件加载配置
func (cm *ConfigManager) LoadFromFile(filename string) error {
    data, err := ioutil.ReadFile(filename)
    if err != nil {
        return fmt.Errorf("读取配置文件失败: %v", err)
    }

    var newConfig Config
    if err := json.Unmarshal(data, &newConfig); err != nil {
        return fmt.Errorf("解析配置文件失败: %v", err)
    }

    cm.UpdateConfig(newConfig)
    return nil
}

// UpdateConfig 更新配置
func (cm *ConfigManager) UpdateConfig(newConfig Config) {
    cm.mu.Lock()
    defer cm.mu.Unlock()

    // 保存当前配置作为历史版本
    cm.versions = append(cm.versions, cm.config)
    if len(cm.versions) > cm.maxVersions {
        cm.versions = cm.versions[1:]
    }

    // 更新配置
    newConfig.Version = cm.config.Version + 1
    newConfig.UpdatedAt = time.Now()
    cm.config = newConfig

    // 通知观察者
    for _, observer := range cm.observers {
        observer.OnConfigChange(newConfig)
    }
}

// GetConfig 获取当前配置
func (cm *ConfigManager) GetConfig() Config {
    cm.mu.RLock()
    defer cm.mu.RUnlock()
    return cm.config
}

// GetValue 获取特定配置项
func (cm *ConfigManager) GetValue(key string) (interface{}, bool) {
    cm.mu.RLock()
    defer cm.mu.RUnlock()
    val, exists := cm.config.Data[key]
    return val, exists
}

// RollbackToVersion 回滚到指定版本
func (cm *ConfigManager) RollbackToVersion(version int) error {
    cm.mu.Lock()
    defer cm.mu.Unlock()

    for _, v := range cm.versions {
        if v.Version == version {
            cm.config = v
            // 通知观察者
            for _, observer := range cm.observers {
                observer.OnConfigChange(v)
            }
            return nil
        }
    }
    return fmt.Errorf("版本 %d 不存在", version)
}

// GetVersionHistory 获取版本历史
func (cm *ConfigManager) GetVersionHistory() []Config {
    cm.mu.RLock()
    defer cm.mu.RUnlock()
    history := make([]Config, len(cm.versions))
    copy(history, cm.versions)
    return history
}

现在让我们创建一个使用示例:

package main

import (
    "fmt"
    "log"
    "time"
)

// ConfigObserver 实现观察者接口
type ConfigObserver struct {
    name string
}

func (o *ConfigObserver) OnConfigChange(newConfig Config) {
    log.Printf("[%s] 配置已更新: 版本=%d, 更新时间=%v\n", 
        o.name, newConfig.Version, newConfig.UpdatedAt)
}

func main() {
    // 创建配置管理器,最多保存5个历史版本
    cm := NewConfigManager(5)

    // 创建并添加观察者
    observer1 := &ConfigObserver{name: "Observer1"}
    observer2 := &ConfigObserver{name: "Observer2"}
    cm.Subscribe(observer1)
    cm.Subscribe(observer2)

    // 更新配置示例
    config1 := Config{
        Data: map[string]interface{}{
            "db_host": "localhost",
            "db_port": 5432,
            "timeout": 30,
        },
    }
    cm.UpdateConfig(config1)

    // 读取配置
    if val, exists := cm.GetValue("db_port"); exists {
        fmt.Printf("数据库端口: %v\n", val)
    }

    // 模拟配置更新
    time.Sleep(time.Second)
    config2 := Config{
        Data: map[string]interface{}{
            "db_host": "localhost",
            "db_port": 5433,  // 更新端口
            "timeout": 60,    // 更新超时时间
        },
    }
    cm.UpdateConfig(config2)

    // 获取版本历史
    history := cm.GetVersionHistory()
    fmt.Printf("配置版本历史:\n")
    for _, v := range history {
        fmt.Printf("版本: %d, 更新时间: %v\n", v.Version, v.UpdatedAt)
    }

    // 回滚到之前的版本
    err := cm.RollbackToVersion(1)
    if err != nil {
        log.Printf("回滚失败: %v\n", err)
    }

    // 取消订阅
    cm.Unsubscribe(observer2)
}

现在让我们创建一个配置文件示例:

{
    "version": 1,
    "updated_at": "2024-03-25T10:00:00Z",
    "data": {
        "app_name": "my_service",
        "http_port": 8080,
        "db_config": {
            "host": "localhost",
            "port": 5432,
            "username": "admin",
            "password": "secret",
            "max_connections": 100
        },
        "cache_config": {
            "enabled": true,
            "ttl_seconds": 300,
            "max_size_mb": 1024
        },
        "log_level": "info"
    }
}

三、核心功能解析

3.1 并发安全的实现

  1. 读写锁使用
  • 使用 sync.RWMutex 实现读写锁控制
  • 读操作使用 RLock()/RUnlock()
  • 写操作使用 Lock()/Unlock()
  1. 原子性保证
  • 配置更新操作是原子的
  • 版本管理操作是原子的
  • 观察者通知是在锁保护下进行的

3.2 配置热更新机制

  1. 文件监控
  • 支持从文件加载配置
  • 可以实现文件变更监控自动加载
  1. 版本控制
  • 每次更新会生成新版本
  • 保存历史版本便于回滚
  • 控制最大版本数量避免内存泄露

3.3 观察者模式实现

  1. 订阅机制
  • 支持多个观察者订阅配置变更
  • 提供订阅和取消订阅的接口
  • 配置变更时自动通知所有观察者
  1. 通知实现
  • 异步通知避免阻塞
  • 保证通知的可靠性
  • 支持自定义通知处理

四、流程图

以下是配置更新的主要流程:
在这里插入图片描述

五、性能优化建议

  1. 读写分离优化
  • 使用读写锁而不是互斥锁
  • 多个读操作可以并发执行
  • 写操作时保证数据一致性
  1. 内存优化
  • 控制历史版本数量
  • 及时清理不再使用的版本
  • 使用指针而不是值拷贝
  1. 通知机制优化
  • 使用channel进行异步通知
  • 避免在锁内进行耗时操作
  • 实现通知的超时机制

六、最佳实践建议

  1. 配置定期持久化
// 定期将配置保存到文件
func (cm *ConfigManager) StartAutoSave(filename string, interval time.Duration) {
    go func() {
        ticker := time.NewTicker(interval)
        defer ticker.Stop()
        
        for range ticker.C {
            cm.mu.RLock()
            data, err := json.MarshalIndent(cm.config, "", "    ")
            cm.mu.RUnlock()
            
            if err == nil {
                ioutil.WriteFile(filename, data, 0644)
            }
        }
    }()
}
  1. 优雅的错误处理
// 配置更新时的错误处理
func (cm *ConfigManager) SafeUpdateConfig(newConfig Config) error {
    if err := validateConfig(newConfig); err != nil {
        return fmt.Errorf("配置验证失败: %v", err)
    }
    
    cm.UpdateConfig(newConfig)
    return nil
}

func validateConfig(config Config) error {
    // 配置验证逻辑
    if config.Data == nil {
        return fmt.Errorf("配置数据不能为空")
    }
    return nil
}

让我们继续完成剩余的内容。

七、监控与指标收集实现

让我们添加监控指标收集功能:

package configmanager

import (
    "sync/atomic"
    "time"
)

// ConfigMetrics 配置管理器的监控指标
type ConfigMetrics struct {
    UpdateCount    int64     // 配置更新次数
    RollbackCount  int64     // 配置回滚次数
    ReadCount      int64     // 配置读取次数
    LastUpdateTime time.Time // 最后更新时间
    ErrorCount     int64     // 错误次数
}

// MetricsCollector 指标收集器
type MetricsCollector struct {
    metrics ConfigMetrics
}

func NewMetricsCollector() *MetricsCollector {
    return &MetricsCollector{
        metrics: ConfigMetrics{
            LastUpdateTime: time.Now(),
        },
    }
}

func (mc *MetricsCollector) IncrementUpdateCount() {
    atomic.AddInt64(&mc.metrics.UpdateCount, 1)
    mc.metrics.LastUpdateTime = time.Now()
}

func (mc *MetricsCollector) IncrementRollbackCount() {
    atomic.AddInt64(&mc.metrics.RollbackCount, 1)
}

func (mc *MetricsCollector) IncrementReadCount() {
    atomic.AddInt64(&mc.metrics.ReadCount, 1)
}

func (mc *MetricsCollector) IncrementErrorCount() {
    atomic.AddInt64(&mc.metrics.ErrorCount, 1)
}

func (mc *MetricsCollector) GetMetrics() ConfigMetrics {
    return ConfigMetrics{
        UpdateCount:    atomic.LoadInt64(&mc.metrics.UpdateCount),
        RollbackCount:  atomic.LoadInt64(&mc.metrics.RollbackCount),
        ReadCount:      atomic.LoadInt64(&mc.metrics.ReadCount),
        ErrorCount:     atomic.LoadInt64(&mc.metrics.ErrorCount),
        LastUpdateTime: mc.metrics.LastUpdateTime,
    }
}

// 更新ConfigManager结构体,添加指标收集器
type ConfigManager struct {
    mu          sync.RWMutex
    config      Config
    observers   []Observer
    versions    []Config
    maxVersions int
    metrics     *MetricsCollector
}

// 更新NewConfigManager函数
func NewConfigManager(maxVersions int) *ConfigManager {
    return &ConfigManager{
        config: Config{
            Version:   0,
            UpdatedAt: time.Now(),
            Data:      make(map[string]interface{}),
        },
        versions:    make([]Config, 0),
        maxVersions: maxVersions,
        metrics:     NewMetricsCollector(),
    }
}

// 添加获取指标的方法
func (cm *ConfigManager) GetMetrics() ConfigMetrics {
    return cm.metrics.GetMetrics()
}

八、配置文件监控实现

添加配置文件自动监控功能:

package configmanager

import (
    "crypto/md5"
    "fmt"
    "io/ioutil"
    "log"
    "time"
)

type ConfigWatcher struct {
    filename     string
    checksum     [16]byte
    interval     time.Duration
    stopChan     chan struct{}
    configManager *ConfigManager
}

func NewConfigWatcher(filename string, interval time.Duration, cm *ConfigManager) *ConfigWatcher {
    return &ConfigWatcher{
        filename:      filename,
        interval:      interval,
        stopChan:      make(chan struct{}),
        configManager: cm,
    }
}

func (w *ConfigWatcher) Start() error {
    // 初始化checksum
    content, err := ioutil.ReadFile(w.filename)
    if err != nil {
        return fmt.Errorf("初始化配置监控失败: %v", err)
    }
    w.checksum = md5.Sum(content)

    go w.watch()
    return nil
}

func (w *ConfigWatcher) Stop() {
    close(w.stopChan)
}

func (w *ConfigWatcher) watch() {
    ticker := time.NewTicker(w.interval)
    defer ticker.Stop()

    for {
        select {
        case <-ticker.C:
            w.checkConfiguration()
        case <-w.stopChan:
            log.Println("配置文件监控已停止")
            return
        }
    }
}

func (w *ConfigWatcher) checkConfiguration() {
    content, err := ioutil.ReadFile(w.filename)
    if err != nil {
        log.Printf("读取配置文件失败: %v", err)
        return
    }

    newChecksum := md5.Sum(content)
    if newChecksum != w.checksum {
        log.Println("检测到配置文件变更,正在重新加载")
        
        if err := w.configManager.LoadFromFile(w.filename); err != nil {
            log.Printf("重新加载配置失败: %v", err)
            return
        }
        
        w.checksum = newChecksum
        log.Println("配置文件已成功重新加载")
    }
}

// 在ConfigManager中添加文件监控功能
func (cm *ConfigManager) StartFileWatcher(filename string, interval time.Duration) (*ConfigWatcher, error) {
    watcher := NewConfigWatcher(filename, interval, cm)
    if err := watcher.Start(); err != nil {
        return nil, err
    }
    return watcher, nil
}

九、完整使用示例

让我们看一个包含所有功能的完整示例:

package main

import (
    "fmt"
    "log"
    "time"
)

type ServiceConfig struct {
    name string
}

func (s *ServiceConfig) OnConfigChange(newConfig Config) {
    log.Printf("[%s] 接收到配置更新通知: 版本=%d\n", s.name, newConfig.Version)
}

func main() {
    // 创建配置管理器
    cm := NewConfigManager(5)

    // 添加配置观察者
    service1 := &ServiceConfig{name: "Service1"}
    service2 := &ServiceConfig{name: "Service2"}
    cm.Subscribe(service1)
    cm.Subscribe(service2)

    // 启动配置文件监控
    watcher, err := cm.StartFileWatcher("config.json", 5*time.Second)
    if err != nil {
        log.Fatalf("启动配置监控失败: %v", err)
    }
    defer watcher.Stop()

    // 模拟配置更新
    go func() {
        for i := 0; i < 3; i++ {
            time.Sleep(2 * time.Second)
            newConfig := Config{
                Data: map[string]interface{}{
                    "app_name": fmt.Sprintf("my_service_%d", i),
                    "version":  fmt.Sprintf("1.%d.0", i),
                    "port":     8080 + i,
                },
            }
            cm.UpdateConfig(newConfig)
        }
    }()

    // 监控配置指标
    go func() {
        ticker := time.NewTicker(1 * time.Second)
        defer ticker.Stop()

        for range ticker.C {
            metrics := cm.GetMetrics()
            log.Printf("配置指标 - 更新次数: %d, 回滚次数: %d, 读取次数: %d, 最后更新时间: %v\n",
                metrics.UpdateCount,
                metrics.RollbackCount,
                metrics.ReadCount,
                metrics.LastUpdateTime)
        }
    }()

    // 模拟配置读取
    go func() {
        for {
            time.Sleep(500 * time.Millisecond)
            if val, exists := cm.GetValue("app_name"); exists {
                log.Printf("当前应用名称: %v\n", val)
            }
        }
    }()

    // 运行一段时间后退出
    time.Sleep(10 * time.Second)
    log.Println("程序退出")
}

十、单元测试

为配置管理器编写完整的单元测试:

package configmanager

import (
    "testing"
    "time"
)

type mockObserver struct {
    notifications int
    lastConfig   Config
}

func (m *mockObserver) OnConfigChange(config Config) {
    m.notifications++
    m.lastConfig = config
}

func TestConfigManager(t *testing.T) {
    // 测试配置更新
    t.Run("TestConfigUpdate", func(t *testing.T) {
        cm := NewConfigManager(5)
        observer := &mockObserver{}
        cm.Subscribe(observer)

        config := Config{
            Data: map[string]interface{}{
                "test_key": "test_value",
            },
        }

        cm.UpdateConfig(config)

        if observer.notifications != 1 {
            t.Errorf("期望收到1次通知,实际收到%d次", observer.notifications)
        }

        if val, exists := cm.GetValue("test_key"); !exists || val != "test_value" {
            t.Error("配置更新失败")
        }
    })

    // 测试版本控制
    t.Run("TestVersionControl", func(t *testing.T) {
        cm := NewConfigManager(3)
        
        // 更新多个版本
        for i := 0; i < 5; i++ {
            cm.UpdateConfig(Config{
                Data: map[string]interface{}{
                    "version": i,
                },
            })
        }

        history := cm.GetVersionHistory()
        if len(history) != 3 {
            t.Errorf("期望保留3个版本,实际保留%d个", len(history))
        }
    })

    // 测试回滚功能
    t.Run("TestRollback", func(t *testing.T) {
        cm := NewConfigManager(5)
        
        // 创建初始版本
        initialConfig := Config{
            Data: map[string]interface{}{
                "key": "initial",
            },
        }
        cm.UpdateConfig(initialConfig)
        
        // 创建新版本
        newConfig := Config{
            Data: map[string]interface{}{
                "key": "new",
            },
        }
        cm.UpdateConfig(newConfig)

        // 回滚到初始版本
        err := cm.RollbackToVersion(1)
        if err != nil {
            t.Errorf("回滚失败: %v", err)
        }

        if val, _ := cm.GetValue("key"); val != "initial" {
            t.Error("回滚后配置值不正确")
        }
    })

    // 测试并发安全性
    t.Run("TestConcurrency", func(t *testing.T) {
        cm := NewConfigManager(5)
        done := make(chan bool)

        // 并发读取
        for i := 0; i < 10; i++ {
            go func() {
                for j := 0; j < 100; j++ {
                    cm.GetConfig()
                }
                done <- true
            }()
        }

        // 并发写入
        go func() {
            for i := 0; i < 100; i++ {
                cm.UpdateConfig(Config{
                    Data: map[string]interface{}{
                        "key": i,
                    },
                })
            }
            done <- true
        }()

        // 等待所有goroutine完成
        for i := 0; i < 11; i++ {
            <-done
        }
    })
}

十一、总结和最佳实践

11.1 关键技术点

  1. 使用读写锁保证并发安全
  2. 实现观察者模式进行配置变更通知
  3. 使用原子操作进行指标收集
  4. 实现版本控制和回滚功能
  5. 支持配置文件自动监控和热更新

11.2 性能优化要点

  1. 读写分离,优化并发性能
  2. 合理控制历史版本数量
  3. 异步处理配置变更通知
  4. 使用缓存优化频繁读取的配置

11.3 使用建议

  1. 定期备份配置文件
  2. 实现配置验证机制
  3. 添加必要的日志记录
  4. 合理设置文件监控间隔
  5. 实现配置的数据验证

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

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

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

相关文章

游戏陪玩系统开发功能需求分析

电竞游戏陪玩系统是一种专门为游戏玩家提供陪伴、指导和互动服务的平台。这类系统通常通过专业的陪玩师&#xff08;也称为陪练师&#xff09;为玩家提供一对一或多对一的游戏陪伴服务&#xff0c;帮助玩家提升游戏技能、享受游戏乐趣&#xff0c;甚至解决游戏中的各种问题。电…

Idea修改Commit Changes模式、idea使用git缺少部分Commit Changes

文章目录 一、模式一1、页面效果如下2、如何打开为这种样式&#xff1f; 二、模式二1、页面效果如下2、如何打开为这种样式&#xff1f; 三、总结 前言&#xff1a;Idea中代码提交到git库时的commit Change有两种模式&#xff0c;每种模式的界面及功能都不太一样。 Commit Cha…

飞书会话消息左右排列

飞书会话消息左右排列 1. 飞书登录后&#xff0c;点击头像&#xff0c;弹出菜单有个按钮设置 2. 3.

VUE3项目 关于金额:分转化为元 ;元转化为分;

1.在components 文件夹下新建moneyHandle.ts 文件 2.ts文件中写如下代码&#xff08;保留两位小数&#xff09; //分转化为元 - 正则解决精度 export const regFenToYuan (fen:any) >{var num fen;numfen*0.01;num;var reg num.indexOf(.) >-1 ? /(\d{1,3})(?(?:…

【linux学习指南】初识Linux进程信号与使用

文章目录 &#x1f4dd;信号快速认识&#x1f4f6;⽣活⻆度的信号&#x1f4f6; 技术应⽤⻆度的信号&#x1f309; 前台进程&#xff08;键盘&#xff09;&#x1f309;⼀个系统函数 &#x1f4f6;信号概念&#x1f4f6;查看信号 &#x1f320; 信号处理&#x1f309; 忽略此信…

蒙特卡洛方法(Monte Carlo,MC)

目录 1 序言 2 Monte Carlo法计算积分 3 最优化计算Monte Carlo法 1 序言 蒙特卡罗方法(Monte Carlo)是由冯诺依曼和乌拉姆等人发明的&#xff0c;“蒙特卡罗”这个名字是出自摩纳哥的蒙特卡罗赌场&#xff0c;这个方法是一类基于概率的方法的统称。是一种应用随机数来进行…

【ROS2】ROS2 构建系统 colcon 介绍、安装与使用

目录 一、ament 与 colcon二、colcon 模块化安装三、colcon 基本使用介绍3.1 常用命令构建工作空间清理构建结果构建特定的包指定构建系统并行构建扩展构建选项 3.2 其他命令列出所有可用的包忽略某些包查看colcon文档 一、ament 与 colcon ROS2采用了新的编译系统Ament&#…

Unity 2020、2021、2022、2023、6000下载安装

Unity 2020、2021、2022、2023、6000 下载安装 以Unity 6000.0.24fc1下载安装为例&#xff1a; 打开 https://unity.cn/ 优三缔 官方网站&#xff1b; 点击【产品列表】→点击【查看更多】→选择自己需要的版本→点【开始使用】 点击【从Unity Hub下载】 以Windows为例&am…

python自定义枚举类的试验与思考

一 现象 在python的3.4版本之前&#xff0c;是没有枚举类的。 所以&#xff0c;我自定义实现了一个enum类&#xff0c;目录如下&#xff1a; 代码如下&#xff1a; class enum(set):def __getattr__(self, name):if name in self:return nameraise AttributeErrorif __name_…

AIGC实践-使用Amazon Bedrock的SDXL模型进行文生图

一、Bedrock 简介 Amazon Bedrock 是 Amazon Web Services (AWS) 提供的一种生成式 AI 服务。通过 Bedrock&#xff0c;用户可以方便地使用多种基础模型&#xff08;Foundation Models&#xff09;&#xff0c;包括 OpenAI 的 GPT、Anthropic 的 Claude 等。这些模型可以用于各…

【MySQL】sql注入相关内容

【MySQL】sql注入相关内容 1. 为什么使用sql注入的时候&#xff0c;url传值的时候要使用–而不是– 使用–进行注释的时候需要在后面加一个空格才可以被认为是注释&#xff0c;url传值的过程中会将空格自动忽略&#xff0c;使用则可以在传输中保留为空格符号。&#xff08;同…

【YOLO】深入理解 CSP 瓶颈模块的变种:Bottleneck、C3、C3k、C2f 和 C3k2

深入理解 CSP 瓶颈模块的变种&#xff1a;Bottleneck、C3、C3k、C2f 和 C3k2 从 YOLOv3 到 YOLOv11&#xff0c;Ultralytics 团队结合当时的主流结构提出了各种适用于 YOLO 的模块&#xff0c;涵盖了不同的创新和优化思路&#xff0c;从而应对不断变化的目标检测需求。这些模块…

Redis中的数据结构详解

文章目录 Redis中的数据结构详解一、引言二、Redis 数据结构1、String&#xff08;字符串&#xff09;1.1、代码示例 2、List&#xff08;列表&#xff09;2.1、代码示例 3、Set&#xff08;集合&#xff09;3.1、代码示例 4、Hash&#xff08;散列&#xff09;4.1、代码示例 5…

计算机的错误计算(一百六十六)

摘要 探讨 MATLAB 关于算式 的计算误差。 例1. 已知 计算 直接贴图吧&#xff1a; 然而&#xff0c;16位的正确结果为 -0.9765626220703239e-21&#xff08;ISRealsoft 提供&#xff09;。这样&#xff0c;MATLAB输出的有效数字的错误率为 (16-2)/16 87.5% . 注&…

大模型时代的具身智能系列专题(十五)

Shubhangi Sinha团队 Shubhangi Sinha是康奈尔大学计算机科学系助理教授。在加入康奈尔大学之前&#xff0c;Tapo 是华盛顿大学计算机科学与工程专业的 NIH Ruth L. Kirschstein NRSA 博士后研究员。他在佐治亚理工学院获得了机器人学博士学位。他之前还曾在迪士尼研究中心工作…

【软件入门】Git快速入门

Git快速入门 文章目录 Git快速入门0.前言1.安装和配置2.新建版本库2.1.本地创建2.2.云端下载 3.版本管理3.1.添加和提交文件3.2.回退版本3.2.1.soft模式3.2.2.mixed模式3.2.3.hard模式3.2.4.使用场景 3.3.查看版本差异3.4.忽略文件 4.云端配置4.1.Github4.1.1.SSH配置4.1.2.关联…

鱼眼相机模型-MEI

参考文献&#xff1a; Single View Point Omnidirectional Camera Calibration from Planar Grids 1. 相机模型如下&#xff1a; // 相机坐标系下的点投影到畸变图像// 输入&#xff1a;相机坐标系点坐标cam 输出&#xff1a; 畸变图像素点坐标disPtvoid FisheyeCamAdapter::…

C++网络编程之多播

概述 在移动互联网时代&#xff0c;随着多媒体应用的日益普及&#xff0c;如何高效地将数据传输给多个接收者成为了网络通信领域的一个重要课题。多播&#xff08;英文为Multicast&#xff09;作为一种高效的网络通信方式&#xff0c;可以将数据同时发送到多个接收者&#xff0…

计算机毕业设计Python音乐推荐系统 机器学习 深度学习 音乐可视化 音乐爬虫 知识图谱 混合神经网络推荐算法 大数据毕设

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

直播技术-Android基础框架

目录 &#xff08;一&#xff09;直播间架构 &#xff08;二&#xff09;核心任务调度机制 &#xff08;1&#xff09;复制从滑动直播间加载流程 &#xff08;2&#xff09;核心任务调度机制-代码设计 &#xff08;3&#xff09;核心任务调度机制-接入指南 (三&#xff0…