为什么此代码中的锁定不起作用
本文关键字:锁定 不起作用 代码 为什么 | 更新日期: 2023-09-27 18:33:08
用这段代码做一个非常基本的记录器:
lock (string.Concat("LogWritter_", this.FileName))
{
using (var fileStream = File.Open(this.FileName, FileMode.Append, FileAccess.Write, FileShare.Read))
{
using (var w = new StreamWriter(fileStream))
{
w.Write(message);
}
}
}
当我同时从几个线程尝试它时,我很快就会收到错误:
The process can't access the file because its being used by another file.
为什么锁不阻止线程同时访问文件?
线程对同一文件调用同一实例还是不同实例并不重要。我也认为这可能是因为在 Windows 中编写文件时有一些延迟,但在 Linux 上也会发生同样的事情。
您正在锁定一个临时字符串。您必须引入一个要锁定的静态对象。
创建一个Dictionary<string,object>
并将锁定对象存储在那里,并将文件路径作为键。
不久前,我遇到了同样的问题:
按字符串锁定。这是安全/理智的吗?
C# lock 语句在对象上放置一个锁,而不是字符串的唯一性。因此,由于您是动态连接两个字符串的,因此您实际上每次都在创建一个新对象,因此每个锁都是唯一的。即使您每次都访问相同的文件,"A"+"B"也会产生一些新的不可变字符串;"A"+"B"再次产生另一个新对象。
你只是锁定了一个动态创建的字符串("LogWritter_" + this.FileName
)!每个线程将创建另一个线程。改为创建一个公共锁对象
public static readonly object fileLock = new object();
...
lock (fileLock) {
...
}
如果要为不同的文件创建不同的锁,则必须将它们存储在将由所有线程使用的集合中。
如果您使用的是 .NET Framework 4.0,则可以使用 ConcurrentDictionary<TKey, TValue>
。否则,您将不得不锁定对正常Dictionary<TKey, TValue>
的访问
public static readonly ConcurrentDictionary<string,object> fileLocks =
new ConcurrentDictionary<string,object>();
...
object lockObject = fileLocks.GetOrAdd(filename, k => new object());
lock (lockObject) {
...
}
更新
如果要比较两个字符串的引用,则必须使用
Object.ReferenceEquals(s1, s2)
哪里
string s1 = "Hello";
string s2 = "Hello";
Console.WriteLine(Object.ReferenceEquals(s1, s2)); // ===> true
string s3 = s1 + " World!";
string s4 = s2 + " World!";
Console.WriteLine(s3 == s4); // ===> true
Console.WriteLine(Object.ReferenceEquals(s3, s4)); // ===> false
在编译时创建的字符串被暂留,即将为相等的字符串创建单个字符串常量。但是,在运行时创建的字符串将创建为单独的和不同的对象!
字符串的哈希代码是根据字符串的字符计算的,而不是根据它们的引用计算的。
试试这段代码。当第一个线程进来并计算 字符串。Concat("LogWritter_",这个。文件名)它对此字符串设置了一个锁。第二个线程也将计算相同的字符串值,但字符串将有所不同。如果你使用 ==,Equals() 或 GetHashCode() 比较字符串,你会看到两个字符串是相同的,因为 == 和 Equals() 对于字符串类来说是重载的。 但是如果你要检查 ReferenceEquals(),那么它会返回 false。这意味着两个字符串都有不同的引用。这就是为什么第一个线程锁定在一个字符串对象上,第二个线程锁定第二个字符串对象并且出现错误的原因。
class Program
{
public static void Main(string[] args)
{
string locker = "str", temp = "temp";
string locker1 = locker + temp;
string locker2 = locker + temp;
Console.WriteLine("HashCode{0} {1}", locker1.GetHashCode(), locker2.GetHashCode());
Console.WriteLine("Equals {0}", locker1.Equals(locker2));
Console.WriteLine("== {0}", locker1 == locker2);
Console.WriteLine("ReferenceEquals {0}", ReferenceEquals(locker1, locker2));
app.Program p = new Program();
Action<string> threadCall = p.Run;
threadCall.BeginInvoke(locker1, null, null);
threadCall.BeginInvoke(locker2, null, null);
Console.Read();
}
public void Run(string str)
{
lock (str)
{
Console.WriteLine("im in");
Thread.Sleep(4000);
Console.WriteLine("print from thread id {0}", Thread.CurrentThread.ManagedThreadId);
}
}
}