![](/img/trans.png)
[英]Locking to make a class threadsafe with a C# example or is this class threadsafe?
[英]Best C# solution for multithreaded threadsafe read/write locking?
在C#中的多线程环境中,对最安静(和最短)的方式执行锁定对static
成员的读/写访问是什么?
是否可以在类级别上执行线程安全锁定和解锁(因此,每次需要静态成员访问时,我都不会继续重复锁定/解锁代码)?
编辑 :示例代码会很棒:)
编辑 :我应该使用volatile关键字或Thread.MemoryBarrier()来避免多处理器缓存还是不必要? 据Jon Skeet说,只有那些会让其他处理器看到变化吗? (另被问及这在这里 )。
对于小值(基本上任何可以声明为volatile的字段),您可以执行以下操作:
private static volatile int backingField;
public static int Field
{
get { return backingField; }
set { backingField = value; }
}
对于大值,如果值大于32位机器上的32位或64位机器上的64位,则赋值将不是原子的。 参见ECMA 335 12.6.6规范。 因此,对于引用类型和大多数内置值类型,赋值是原子的,但是如果你有一些大的结构,比如:
struct BigStruct
{
public long value1, valuea0a, valuea0b, valuea0c, valuea0d, valuea0e;
public long value2, valuea0f, valuea0g, valuea0h, valuea0i, valuea0j;
public long value3;
}
在这种情况下,您需要在get访问器周围进行某种锁定。 您可以使用ReaderWriterLockSlim
进行此操作,我将在下面演示。 Joe Duffy对使用ReaderWriterLockSlim
和ReaderWriterLock
有建议 :
private static BigStruct notSafeField;
private static readonly ReaderWriterLockSlim slimLock =
new ReaderWriterLockSlim();
public static BigStruct Safe
{
get
{
slimLock.EnterReadLock();
var returnValue = notSafeField;
slimLock.ExitReadLock();
return returnValue;
}
set
{
slimLock.EnterWriteLock();
notSafeField = value;
slimLock.ExitWriteLock();
}
}
不安全的Get-Accessor演示
这是我用来表示在get-accessor中不使用锁时缺乏原子性的代码:
private static readonly object mutexLock = new object();
private static BigStruct notSafeField;
public static BigStruct NotSafe
{
get
{
// this operation is not atomic and not safe
return notSafeField;
}
set
{
lock (mutexLock)
{
notSafeField = value;
}
}
}
public static void Main(string[] args)
{
var t = new Thread(() =>
{
while (true)
{
var current = NotSafe;
if (current.value2 != (current.value1 * 2)
|| current.value3 != (current.value1 * 5))
{
throw new Exception(String.Format("{0},{1},{2}", current.value1, current.value2, current.value3));
}
}
});
t.Start();
for(int i=0; i<50; ++i)
{
var w = new Thread((state) =>
{
while(true)
{
var index = (int) state;
var newvalue = new BigStruct();
newvalue.value1 = index;
newvalue.value2 = index * 2;
newvalue.value3 = index * 5;
NotSafe = newvalue;
}
});
w.Start(i);
}
Console.ReadLine();
}
最安全和最短的方法是创建一个Object
类型的私有静态字段,该字段仅用于锁定(将其视为“pad-lock”对象)。 使用此字段并仅锁定此字段,因为这会阻止其他类型锁定代码,然后锁定您所执行的相同类型。
如果您锁定类型本身,则存在另一种类型也将决定锁定您的类型的风险,这可能会造成死锁。
这是一个例子:
class Test
{
static readonly Object fooLock = new Object();
static String foo;
public static String Foo
{
get { return foo; }
set
{
lock (fooLock)
{
foo = value;
}
}
}
}
请注意,我已经创建了一个用于锁定foo
的私有静态字段 - 我使用该字段来锁定该字段上的写入操作。
虽然您可以使用单个互斥锁来控制对类的所有访问(有效地序列化对类的访问),但我建议您研究静态类,确定哪些成员正在使用的位置和方式以及使用一个或多个ReaderWriterLock ( MSDN文档中的代码示例,它提供了对多个读者的访问,但同时只能访问一个编写者。
这样你就会有一个细粒度的多线程类,它只会阻止写入,但同时会允许多个读者,并允许在读取另一个不相关的成员时写入一个成员。
class LockExample {
static object lockObject = new object();
static int _backingField = 17;
public static void NeedsLocking() {
lock(lockObject) {
// threadsafe now
}
}
public static int ReadWritePropertyThatNeedsLocking {
get {
lock(lockObject) {
// threadsafe now
return _backingField;
}
}
set {
lock(lockObject) {
// threadsafe now
_backingField = value;
}
}
}
}
lock
专门为此目的而创建的对象而不是typeof(LockExample)
以防止其他人锁定LockExample
的类型对象的死锁情况。
是否可以在类级别上执行线程安全锁定和解锁(因此,每次需要静态成员访问时,我都不会继续重复锁定/解锁代码)?
只lock
你需要的地方,并在被叫者内部进行,而不是要求呼叫者进行lock
。
您应该根据需要在静态访问器内的每个静态成员访问上锁定/解锁。
保留一个私有对象用于锁定,并根据需要锁定。 这使锁定尽可能细,这非常重要。 它还使锁定内部保持静态类成员。 如果你锁定在班级,你的调用者将负责锁定,这会损害可用性。
我感谢大家,我很高兴分享这个演示程序,受上述贡献的启发,运行3种模式(不安全,互斥,苗条)。
请注意,设置“Silent = false”将导致线程之间完全没有冲突。 使用此“Silent = false”选项可以在控制台中写入所有线程。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace Test
{
class Program
{
//------------------------------------------------------------------------------
// Configuration.
const bool Silent = true;
const int Nb_Reading_Threads = 8;
const int Nb_Writing_Threads = 8;
//------------------------------------------------------------------------------
// Structured data.
public class Data_Set
{
public const int Size = 20;
public long[] T;
public Data_Set(long t)
{
T = new long[Size];
for (int i = 0; i < Size; i++)
T[i] = t;
}
public Data_Set(Data_Set DS)
{
Set(DS);
}
public void Set(Data_Set DS)
{
T = new long[Size];
for (int i = 0; i < Size; i++)
T[i] = DS.T[i];
}
}
private static Data_Set Data_Sample = new Data_Set(9999);
//------------------------------------------------------------------------------
// SAFE process.
public enum Mode { Unsafe, Mutex, Slim };
public static Mode Lock_Mode = Mode.Unsafe;
private static readonly object Mutex_Lock = new object();
private static readonly ReaderWriterLockSlim Slim_Lock = new ReaderWriterLockSlim();
public static Data_Set Safe_Data
{
get
{
switch (Lock_Mode)
{
case Mode.Mutex:
lock (Mutex_Lock)
{
return new Data_Set(Data_Sample);
}
case Mode.Slim:
Slim_Lock.EnterReadLock();
Data_Set DS = new Data_Set(Data_Sample);
Slim_Lock.ExitReadLock();
return DS;
default:
return new Data_Set(Data_Sample);
}
}
set
{
switch (Lock_Mode)
{
case Mode.Mutex:
lock (Mutex_Lock)
{
Data_Sample.Set(value);
}
break;
case Mode.Slim:
Slim_Lock.EnterWriteLock();
Data_Sample.Set(value);
Slim_Lock.ExitWriteLock();
break;
default:
Data_Sample.Set(value);
break;
}
}
}
//------------------------------------------------------------------------------
// Main function.
static void Main(string[] args)
{
// Console.
const int Columns = 120;
const int Lines = (Silent ? 50 : 500);
Console.SetBufferSize(Columns, Lines);
Console.SetWindowSize(Columns, 40);
// Threads.
const int Nb_Threads = Nb_Reading_Threads + Nb_Writing_Threads;
const int Max = (Silent ? 50000 : (Columns * (Lines - 5 - (3 * Nb_Threads))) / Nb_Threads);
while (true)
{
// Console.
Console.Clear();
Console.WriteLine("");
switch (Lock_Mode)
{
case Mode.Mutex:
Console.WriteLine("---------- Mutex ----------");
break;
case Mode.Slim:
Console.WriteLine("---------- Slim ----------");
break;
default:
Console.WriteLine("---------- Unsafe ----------");
break;
}
Console.WriteLine("");
Console.WriteLine(Nb_Reading_Threads + " reading threads + " + Nb_Writing_Threads + " writing threads");
Console.WriteLine("");
// Flags to monitor all threads.
bool[] Completed = new bool[Nb_Threads];
for (int i = 0; i < Nb_Threads; i++)
Completed[i] = false;
// Threads that change the values.
for (int W = 0; W < Nb_Writing_Threads; W++)
{
var Writing_Thread = new Thread((state) =>
{
int t = (int)state;
int u = t % 10;
Data_Set DS = new Data_Set(t + 1);
try
{
for (int k = 0; k < Max; k++)
{
Safe_Data = DS;
if (!Silent) Console.Write(u);
}
}
catch (Exception ex)
{
Console.WriteLine("\r\n" + "Writing thread " + (t + 1) + " / " + ex.Message + "\r\n");
}
Completed[Nb_Reading_Threads + t] = true;
});
Writing_Thread.Start(W);
}
// Threads that read the values.
for (int R = 0; R < Nb_Reading_Threads; R++)
{
var Reading_Thread = new Thread((state) =>
{
int t = (int)state;
char u = (char)((int)('A') + (t % 10));
try
{
for (int j = 0; j < Max; j++)
{
Data_Set DS = Safe_Data;
for (int i = 0; i < Data_Set.Size; i++)
{
if (DS.T[i] != DS.T[0])
{
string Log = "";
for (int k = 0; k < Data_Set.Size; k++)
Log += DS.T[k] + " ";
throw new Exception("Iteration " + (i + 1) + "\r\n" + Log);
}
}
if (!Silent) Console.Write(u);
}
}
catch (Exception ex)
{
Console.WriteLine("\r\n" + "Reading thread " + (t + 1) + " / " + ex.Message + "\r\n");
}
Completed[t] = true;
});
Reading_Thread.Start(R);
}
// Wait for all threads to complete.
bool All_Completed = false;
while (!All_Completed)
{
All_Completed = true;
for (int i = 0; i < Nb_Threads; i++)
All_Completed &= Completed[i];
}
// END.
Console.WriteLine("");
Console.WriteLine("Done!");
Console.ReadLine();
// Toogle mode.
switch (Lock_Mode)
{
case Mode.Unsafe:
Lock_Mode = Mode.Mutex;
break;
case Mode.Mutex:
Lock_Mode = Mode.Slim;
break;
case Mode.Slim:
Lock_Mode = Mode.Unsafe;
break;
}
}
}
}
}
锁定静态方法听起来是个坏主意,但有一点,如果你从类构造函数中使用这些静态方法,你可能会遇到一些有趣的副作用,因为加载器锁(以及类加载器可以忽略其他锁的事实)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.