`
RednaxelaFX
  • 浏览: 3049224 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

把lock的意思给弄混了 T T

    博客分类:
  • C#
阅读更多
悲剧啊……前几天有个同学不停问我Java里的同步问题,今天写C#的时候却不小心把lock的语义给弄混了。诶,才回答过的问题换个马甲就把我给绊住了 T T

下午写代码的时候,对某个库的实现有点疑问,就钻进去看了下。然后看到一个方法A里是用lock语句包围的,获取了一个锁,其中调用了一个辅助方法B;B也是被lock语句包围的,要获取了同一个锁。我就纳闷这代码是怎么跑得通的,两个方法要同时获取同一个锁不是阻塞了么?
然后我发现我是把lock跟跟线程无关的简单的flag给弄混了 T T

关键是,lock语句、Mutex和Semaphore都是以线程为单位来获取/释放的,而不是以方法之类的为单位。如果一个方法已经获取了某个锁,它调用另一个方法也要获取同一个锁,那么完全没问题,因为方法调用是在同一个线程上的。

把我看到的代码简化一下,状况如下例所示:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;

static class Program {
    static void Main(string[] args) {
        var foo = new ArrayWrapper<string>();
        foo.TrySetValue(2, "Charlie");
        foo.TrySetValue(0, "Alpha");
        foreach (var str in foo) {
            Console.WriteLine(str);
        }
    }
}

class ArrayWrapper<T> : IEnumerable<T>, IEnumerable {
    readonly object LockObject;
    T[] _data;
    
    public ArrayWrapper() {
        LockObject = new object();
        _data = new T[0];
    }
    
    public void TrySetValue(int index, T value) {
        lock (LockObject) {
            Console.WriteLine("acquired lock in TrySetValue");
            var data = _data;
            
            if (index < 0) return;
            if (index >= data.Length) {
                data = PromoteData(data.Length, index + 1);
            }
            data[index] = value;
        }
    }
    
    private T[] PromoteData(int oldLen, int newLen) {
        Debug.Assert(oldLen != newLen);
        
        lock (LockObject) {
            Console.WriteLine("acquired lock in PromoteData");
            if (_data.Length == oldLen) {
                var data = new T[newLen];
                _data.CopyTo(data, 0);
                _data = data;
            }
            return _data;
        }
    }
    
    public IEnumerator<T> GetEnumerator() {
        var data = _data;
        foreach (var item in data) {
            yield return item;
        }
    }
    
    IEnumerator IEnumerable.GetEnumerator() {
        return GetEnumerator();
    }
}


换成Mutex也一样:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

static class Program {
    static void Main(string[] args) {
        var foo = new ArrayWrapper<string>();
        foo.TrySetValue(2, "Charlie");
        foo.TrySetValue(0, "Alpha");
        foreach (var str in foo) {
            Console.WriteLine(str);
        }
    }
}

class ArrayWrapper<T> : IEnumerable<T>, IEnumerable {
    readonly Mutex _mutex;
    T[] _data;
    
    public ArrayWrapper() {
        _mutex = new Mutex();
        _data = new T[0];
    }
    
    public void TrySetValue(int index, T value) {
        _mutex.WaitOne();
        Console.WriteLine("acquired mutex in TrySetValue");
        var data = _data;
        
        if (index < 0) return;
        if (index >= data.Length) {
            data = PromoteData(data.Length, index + 1);
        }
        data[index] = value;
        _mutex.ReleaseMutex();
    }
    
    private T[] PromoteData(int oldLen, int newLen) {
        Debug.Assert(oldLen != newLen);
        
        _mutex.WaitOne();
        Console.WriteLine("acquired mutex in PromoteData");
        
        T[] data = _data;
        if (_data.Length == oldLen) {
            data = new T[newLen];
            _data.CopyTo(data, 0);
            _data = data;
        }
        _mutex.ReleaseMutex();
        return data;
    }
    
    public IEnumerator<T> GetEnumerator() {
        var data = _data;
        foreach (var item in data) {
            yield return item;
        }
    }
    
    IEnumerator IEnumerable.GetEnumerator() {
        return GetEnumerator();
    }
}


我是想像成类似这样了……(嗯我知道Interlocked.Exchange()和Interlocked.CompareExchange()不一样,只是说类似)
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

static class Program {
    static void Main(string[] args) {
        var foo = new ArrayWrapper<string>();
        foo.TrySetValue(2, "Charlie");
        foo.TrySetValue(0, "Alpha");
        foreach (var str in foo) {
            Console.WriteLine(str);
        }
    }
}

class ArrayWrapper<T> : IEnumerable<T>, IEnumerable {
    int _flag;
    T[] _data;
    
    public ArrayWrapper() {
        _flag = 0;
        _data = new T[0];
    }
    
    public void TrySetValue(int index, T value) {
        var done = false;
        while (!done) { // spin on the lock
            Console.WriteLine("trying to acquire lock in TrySetValue");
            if (0 == Interlocked.Exchange(ref _flag, 1)) {
                Console.WriteLine("acquired mutex in TrySetValue");
                var data = _data;
                
                if (index < 0) return;
                if (index >= data.Length) {
                    data = PromoteData(data.Length, index + 1);
                }
                data[index] = value;
                
                done = true;
                Interlocked.Exchange(ref _flag, 0);
            }
            
            if (!done) Thread.Sleep(500);
        }
    }
    
    private T[] PromoteData(int oldLen, int newLen) {
        Debug.Assert(oldLen != newLen);
        
        T[] data = null;
        var done = false;
        while (!done) { // spin on the lock
            Console.WriteLine("trying to acquire lock in PromoteData");
            if (0 == Interlocked.Exchange(ref _flag, 1)) {
                Console.WriteLine("acquired mutex in PromoteData");
                
                data = _data;
                if (_data.Length == oldLen) {
                    data = new T[newLen];
                    _data.CopyTo(data, 0);
                    _data = data;
                }
                
                done = true;
                Interlocked.Exchange(ref _flag, 0);
            }
            
            if (!done) Thread.Sleep(500);
        }
        return data;
    }
    
    public IEnumerator<T> GetEnumerator() {
        var data = _data;
        foreach (var item in data) {
            yield return item;
        }
    }
    
    IEnumerator IEnumerable.GetEnumerator() {
        return GetEnumerator();
    }
}

这样写的话,“获取”一个锁(也就是把_flag置为1,并且_flag原来的值为0)就不是以线程为单位。于是就死锁了。正常用lock、Mutex、Semaphore不会遇到这种问题……

就算Monitor.Enter里面有用到InterlockedCompareExchange()……Monitor.Enter并不是简单的把SyncBlock里的值在0和1间切换,而是还涉及到线程身份。我一下把这个给忘了,赶紧记下来 TvT
分享到:
评论

相关推荐

    redisson lock和tryLock 分布式锁简单练习

    redisson lock和tryLock 分布式锁简单练习

    AHK-CapsLock修改为回车-左手快捷键.ahk

    关键字-回车映射-ahk回车映射-左手回车 ;;快捷键x说明 上下左右 ... CapsLock+q 箭头上--防止误触屏-CapsLock+q也映射为箭头上 ;; CapsLock+e 删除键-删除前面的-Backspace ;; CapsLock+r 删除键-删除后面的--Del

    Capslock++,Capslock键利器

    标题中的"Capslock++"指的是一个利用AutoHotkey脚本技术改造 Capslock 键的工具,旨在提升用户在计算机操作中的工作效率。Capslock键通常用于切换字母大小写,但通过 Capslock++,我们可以将其功能扩展到更高效的...

    mysql启动提示mysql.host 不存在,启动失败的解决方法

    error 日志当中的记录: [ERROR] Fatal error: Can’t open and lock privilege tables: Table ‘mysql.host’ doesn’t exist 从发了帖子,只有人看,没有人回复,看到这种情况只能自己解决问题了,自己动手...

    lock(this)的使用说明

    "lock(this)的使用说明" lock(this)是C#语言中的一种同步机制,用于确保在多线程环境下对共享资源的访问安全。通过使用lock(this)语句,可以保证在同一时刻只有一个线程可以访问某个资源,防止多个线程同时访问同...

    实现yarnlock与packagelockjson相互转换

    `yarn.lock` 和 `package-lock.json` 文件都是npm和yarn包管理器为了确保项目依赖一致性而生成的文件。本篇文章将详细探讨这两个文件的作用、差异以及如何实现它们之间的相互转换。 ### `yarn.lock` 文件 `yarn....

    JAVA Lock加锁实例

    Lock锁是对象锁,仅在同一对象中,锁才会生效。(不做论证) (以下场景皆为单例模式下运行) lock.lock()的加锁方式,会使后续请求的线程堵塞等待。(方案A) lock.tryLock()的加锁方式,不会堵塞,会立即返回加锁...

    锁实现lock

    pthread_mutex_t lock; pthread_mutex_init(&lock, NULL); ``` 在`lock.cpp`和`lock.h`这两个文件中,`lock.h`可能包含了对外部使用锁的接口声明,而`lock.cpp`则实现了具体的锁操作。例如,一个简单的加锁和解锁...

    gradle lock文件清除

    gradle lock文件清除 gradle lock文件清除 gradle lock文件清除 gradle lock文件清除 gradle lock文件清除 gradle lock文件清除 gradle lock文件清除 gradle lock文件清除 gradle lock文件清除 gradle lock文件清除 ...

    c#线程 lock用法

    `lock`语句是C#中用于实现线程同步的重要工具,主要是为了解决多个线程并发访问共享资源时可能出现的竞争条件(race condition)问题。在描述的场景中,`lock`关键字被用来确保在同一时间只有一个线程可以访问特定的...

    lockfree c#.net

    在IT行业中,"LockFree"是一种并发编程技术,它的核心思想是避免在多线程环境下使用锁来同步对共享资源的访问。LockFree技术在C# .NET框架中也有广泛的应用,可以显著提高多线程环境下的程序性能,降低死锁和竞态...

    lock on 2.0 免1.1补丁

    然而,在升级到2.0版本的过程中,有些用户可能遇到了需要先安装1.1补丁的问题,这给安装过程带来了一定的困扰。为此,专门设计了一个"lock on 2.0 免1.1补丁",旨在简化安装流程,让玩家能够更顺畅地享受游戏。 这...

    自旋锁操作 spin_lock

    4. `spin_lock_bh(spinlock_t *lock)`:与 `spin_lock` 类似,但在获取锁时会禁止软中断。适用于需要在持有锁的同时禁止软中断的场景。 5. `spin_unlock_bh(spinlock_t *lock)`:对应于 `spin_lock_bh` 的解锁操作,...

    C# Lock实例

    在C#编程中,`Lock`是一个至关重要的概念,它涉及到多线程编程中的同步控制。`Lock`机制用于确保在任何给定时间只有一个线程可以访问特定的代码块,从而避免了数据竞争和其他并发问题。这个实例将深入探讨`Lock`的...

    capslock.rar

    标题中的“capslock.rar”指的是一个压缩包文件,其中包含了关于Capslock+软件的相关内容。Capslock+是一款创新的工具,它扩展了键盘上通常不常用到的Capslock键的功能,将其转化为一种强大的操作快捷键。这款软件的...

    lock4j高性能分布式锁 v2.2.6.zip

    《lock4j高性能分布式锁详解》 分布式锁是分布式系统中的关键组件,它在多节点共享资源时确保了数据的一致性和并发控制。lock4j作为一款高性能的分布式锁,为开发者提供了简单易用且高效的解决方案。本文将深入探讨...

    c#lock 演示代码

    在C#编程语言中,`lock`语句是一种同步机制,用于实现线程之间的互斥访问,确保在多线程环境中资源的安全访问。本篇将深入探讨`lock`的使用,以及它在并发编程中的作用。 `lock`关键字是C#中实现线程同步的关键工具...

    彻底搞清楚library cache lock的成因和解决方法

    ### 彻底理解 Oracle 中 Library Cache Lock 的成因与解决办法 #### 一、引言 在日常数据库管理工作中,经常会遇到一些让人头疼的问题,比如会话挂起(hang)。其中一个常见的情况是当用户尝试执行某些操作时,比如...

    .NET LOCK使用方法

    .NET LOCK使用方法 .NET框架中的Lock关键字用于控制多线程对共享资源的访问,确保在同一时间只有一个线程能够执行一个代码块。当一个线程访问被锁定的资源时,其它所有尝试获取锁的线程将被阻塞,直到锁被释放。 ...

Global site tag (gtag.js) - Google Analytics