一个线程更新数据, 多个线程读数据, 这种怎么保证线程安全?

查看 131|回复 11
作者:bthulu   
.Net 相关
线程 0 调用硬件异步 API, 拿到数据后, 从 devices 根据 id 取到 Device 实例, 更新硬件最新数据到这个实例上.
同时有多个监控线程每隔 100 毫秒读取一次所有设备状态, 并根据设备状态执行一次或多次耗时较长的异步操作, 并在异步操作执行完成后, 对硬件数据进行部分更新.
这个要怎么做才能确保线程安全?
    // 设备集中存储处
    ConcurrentDictionary[i] devices = new();
    // 设备类
    public class Device
    {
        public int Id { get; init; }
        public bool Enable { get; set; }
        public string Group { get; init; } = "";
        public int[] Locations { get; init; } = Array.Empty[i]();
        public int Margin { get; set; }
        public int RsCount { get; init; }
        public bool EnableSplit { get; init; }
        public int DynamicMerge { get; set; }
        public int Width { get; set; }
        public int Length { get; set; }
        public int LeftLength { get; set; }
        public int LoadEdge { get; set; }
        public int Dest { get; set; }
    }
    // 数据更新线程相关
    public Thread0Executor()
    {
            public async Task Execute()
        {
            var data = await GetDataFromHardwareApi();
            Update(data, devices);
        }
    }
    // 数据监控处理线程相关
    public MonitorThreadExecutor()
    {
            public async Task Execute()
        {
            Resolve(devices);
            await Operate0();
            DoSomething();
            await Operate1();
            DoSomething();
        }
        
        public async Task Operate0()
        {
            try
            {
                    await CallApi();
                    Update(devices);
            }
            catch()
            {
                    UpdateIfError(devices);
            }
        }
    }
异步方法中根本没办法使用锁, 顶多用用信号量 Semaphore 来代替锁.
这里也不能对整个 Execute 方法用锁. 因为监控线程中的异步操作耗时是不一定的, 可能因为网络问题花个几分钟都有可能.
貌似也没法仅对非异步代码进行加锁, 因为同步异步代码是混杂在一块的, 没法单独对非异步代码进行加锁.
也考虑过弄个类似 ANDROID 里的 UI 线程和子线程的东西, 数据读取和更新都放在 UI 线程里, 异步操作放在子线程里. 但是搞了半天没搞出来.
最后的最后, 实在没办法了, 我在想要不把 Device 的所有属性都加一个 volatile 关键字. 我这里更新数据的时候基本不会看原来数据是多少, 不会出现count++这种情况, 貌似 volatile 是可行的. 但是实际这个 Device 有几十个属性, 并且有一两千个 Device, 如果每个属性都加一个 volatile 关键字, 那就是 2000*50=100 万个属性带 volatile 了. 这会不会极大地影响程序运行性能?

public, int, get, 异步

svnware   
单写多读不就已经是线程安全的了么。。。
wamson   
看标题,寻思,这不就是个读写锁么😳
laminux29   
你这不是一线程更新,多线程读,而是多线程读写。
这种问题,没把握的话,直接丢给 MSSQL ,如果对数据一致性要求严谨,用序列化级别的事务去操作数据。
如果要求不严谨,直接用 EF 的乐观锁或最终一致性。
wayne1007   
double buffer ,写线程 先 load 数据,然后和更新 buffer 的 idx 0->1 或者 1->0
wayne1007   
@wayne1007 读线程,直接按当前的 idx 读数据,就 idx 切换的瞬间,可能不一致,用信号量也不一定能完全保证,看你这个场景够不够用,不用锁的话
bthulu
OP
  
@wamson 读写锁不行的, 异步调用有可能耗时特别长. 总不能某个设备接口耗时过长时, 其他设备都不能用了吧?
namonai   
@svnware 不一样的。比如一段数据,写入一半的时候被读取,读到的就是 broken 的数据。哪怕是对单个字节进行读写操作,也可能存在问题,所以至少要使用原子操作进行保护。
codcrafts   
我没太懂,你这种情况下会有线程安全问题吗?我感觉不会
bthulu
OP
  
@wayne1007 如果用锁, 只要能保证某个线程调用异步操作耗时特别长时, 其他线程可以干活而不是在那干等着就行.
您需要登录后才可以回帖 登录 | 立即注册

返回顶部