排它锁
排它锁三种:lock,Mutex,SpinLock
排它锁锁住的是代码块,而不是代码块里面的变量。当别的线程也想要使用这一部分代码时,就需要判断这个锁可不可以用。
lock语句
class ThreadSafe
{
static readonly object _locker = new object();
static int _val = 0;
static void Go()
{
lock (_locker)
{
_val++;
}
}
}
lock语句的本质
void Fun()
{
Monitor.Enter(_locker);
try
{
x++;
}
finally
{
Monitor.Exit(_locker);
}
}
解决上述转换中异常的问题,C#4.0的重载
bool lockToken = false;
void Fun()
{
Monitor.Enter(_locker, ref lockToken);
try
{
x++;
}
finally
{
if(lockToken)
Monitor.Exit(_locker);
}
}
TryEnter,可以指定时间
选择同步对象
1.如果一个对象在各个线程中都是可见的,那就可以用作同步对象。这个对象必须是引用类型的对象
2.同步对象通常是私有的,一般是实例字段或静态字段
3.同步对象本身也是可以被保护的对象
4.锁不会限制同步对象的访问功能
使用锁的时机
需要访问可写的共享字段,需要加锁
锁与原子性
1.如果使用同一个锁对一组变量的读写操作进行保护,可以认为这些变量的读写操作是原子的。
2.在锁中抛出异常会破坏原子操作。可以先将会抛出异常的操作执行,在进行原子操作。或者在finally中实现回滚操作
嵌套锁
只有最外层的lock退出时,对象才会被解除
当一个锁中方法调用另一个方法时用嵌套锁
死锁
public static void Go1()
{
lock (_locker1)
{
Console.WriteLine("start thread1");
Thread.Sleep(1000);
lock (_locker2)
{
Console.WriteLine("end thread1");
}
}
}
public static void Go2()
{
lock (_locker2)
{
Console.WriteLine("start thread2");
Thread.Sleep(1000);
lock (_locker1)
{
Console.WriteLine("start thread2");
}
}
}
Mutex
可用于进程锁
一般用于保证只能后运行一个应用程序的实例
锁和线程安全性
1.静态成员是保证线程安全的
2.使用不可变对像,就在定义一个都是readonly字段的对象,这个对象再创建后就不能写了,所以在读取时就没有问题了。
非排他锁
信号量
1.有特定的容量,一旦满员之后就不允许其他人进入了,只有当其中的人离开时,才能允许另外一个人进入
static SemaphoreSlim _sem = new SemaphoreSlim(3);
static void Main(string[] args)
{
for (int i = 0; i < 6; i++)
{
new Thread(Enter).Start(i);
}
Console.WriteLine("End");
Console.ReadLine();
}
static void Enter(object id)
{
Console.WriteLine(id + " wants to enter");
_sem.Wait();
Console.WriteLine(id + " is in");
Thread.Sleep(1000);
Console.WriteLine(id + " is leave");
_sem.Release();
}
2.命名的Semaphore可以跨进程使用
读写锁
1.适用情况,读操作大量(线程安全),写操作少量(线程不安全)
2.一个线程持有写锁,则其他的读操做些操作都被阻断。没有线程有写锁,则所有线程都可以获取写锁和读锁
3.ReaderWriterLockSlim在有线程在读的时候是没办法写的,有线程在写的时候也是没办法读的
4.需要添加try/finally保证在异常时也会释放锁
5.可升级锁EnterUpgradeableReadLock。因为读锁时排他的,所以我们只有在必要的时候才使用写锁。我们先使用读锁,在确定需要修改时,再转换为写锁。
static void Write()
{
while (true)
{
_rw.EnterUpgradeableReadLock();
if (_items.Contains(5))
{
_rw.EnterWriteLock();
_items.Add(8);
Thread.Sleep(3000);
_rw.ExitWriteLock();
Thread.Sleep(2000);
}
_rw.ExitUpgradeableReadLock();
}
}
6.递归锁
使用事件等待句柄发送信号
意思是,我已经蓄势待发,只要闸门一开,就随时可以冲出去了。
AutoResetEvent
1.WaitOne,等待闸门开启
2.Set,开闸门
//true会直接打开,使线程无需等待
static EventWaitHandle _waitHandle = new AutoResetEvent(false);
static EventWaitHandle _waitHandle2 = new EventWaitHandle(false,EventResetMode.AutoReset);
static void Main(string[] args)
{
new Thread(Waiter).Start();
Thread.Sleep(5000);
_waitHandle.Set();
Console.WriteLine("End");
Console.ReadLine();
}
static void Waiter()
{
Console.WriteLine("Wait...");
_waitHandle.WaitOne();
Console.WriteLine("Notified");
}
3.可以直接先调用Set,这样第一个线程无需任何等待
4.多次调用set无用,闸门只有一道,每次只能通过一人。
5.Reset可使打开的闸门立即关闭
6.双向信号,需要根据对方的执行情况来再次设置闸门
static EventWaitHandle _waitHandle = new AutoResetEvent(false);
static EventWaitHandle _waitHandle2 = new EventWaitHandle(false,EventResetMode.AutoReset);
static void Main(string[] args)
{
new Thread(Waiter).Start();
_waitHandle.WaitOne();
Thread.Sleep(5000);
_waitHandle2.Set();
_waitHandle.WaitOne();
Thread.Sleep(5000);
_waitHandle2.Set();
Console.WriteLine("End");
Console.ReadLine();
}
static void Waiter()
{
while (true)
{
_waitHandle.Set();
_waitHandle2.WaitOne();
Thread.Sleep(5000);
}
}
ManualResetEvent
开启时,会让任意数量的线程通过。关闭时,没有人能通过,但是都会等待。
static EventWaitHandle _waitHandle = new ManualResetEvent(false);
static EventWaitHandle _waitHandle2 = new EventWaitHandle(false,EventResetMode.ManualReset);
static void Main(string[] args)
{
new Thread(Waiter).Start();
new Thread(Waiter).Start();
new Thread(Waiter).Start();
Thread.Sleep(2000);
_waitHandle.Set();
Console.WriteLine("End");
Console.ReadLine();
}
static void Waiter()
{
_waitHandle.WaitOne();
Console.WriteLine("abc 123");
}
CountdownEvent
可用于等待多个线程,Signal可以使计数递减,Wait则会阻塞。
在CountdownEvent计数没有减至0时,Wait后面的代码永远不会执行
static CountdownEvent countdown = new CountdownEvent(3);
static void Main(string[] args)
{
new Thread(Waiter).Start();
new Thread(Waiter).Start();
new Thread(Waiter).Start();
Thread.Sleep(2000);
countdown.Wait();
Console.WriteLine("End");
Console.ReadLine();
}
static void Waiter()
{
Thread.Sleep(2000);
Console.WriteLine("abc 123");
countdown.Signal();
}
EventWaitHandle
支持跨进程操作
等待句柄和延迟操作
不希望阻塞线程,当句柄接到信号或超时时,该附加操作就会执行
static ManualResetEvent manual = new ManualResetEvent(false);
static void Main(string[] args)
{
RegisteredWaitHandle reg =
ThreadPool.RegisterWaitForSingleObject(manual, Waiter, "some data", 2000, true);
Thread.Sleep(5000);
manual.Set();
Console.WriteLine("main");
Console.WriteLine("End");
Console.ReadLine();
reg.Unregister(manual);
}
static void Waiter(object data, bool timeout)
{
Console.WriteLine(data);
}
WaitAny,WaitAll,SignalAndWait
WaitAny:等待一组句柄中的任意一个句柄
WaitAll:等待一组句柄中的所有句柄
SignalAndWait:先调用一个线程的Set方法,再调用另外一个线程的WaitOne,可以使2个线程在同一时刻交汇
Barrier类
1.允许多个线程在同一时刻汇合
2.指定一个数字,没当调用SignalAndWait的次数到达这个数字之后,才放行。然后再达到这个数字,放行。
static Barrier barrier = new Barrier(2);
static void Main(string[] args)
{
new Thread(Go).Start();
new Thread(Go).Start();
new Thread(Go).Start();
Console.WriteLine("End");
Console.ReadLine();
}
static void Go()
{
for (int i = 0; i < 3; i++)
{
barrier.SignalAndWait();
Console.WriteLine(i + "Go Go Go");
}
}
3.还指定一个后续操作,当放开通行后执行这个操作。这个操作在执行的时候,所有线程都会被阻塞
static Barrier barrier = new Barrier(2, b => Console.WriteLine("sss"));
延迟初始化
Lazy<T>
相当于单例模式中的懒汉模式
//true表示线程安全,false表示线程不安全(就和正常创建一样)
Lazy<A> _a = new Lazy<A>(()=> new A(),true);
public A a { get { return _a.Value; } }
static void Main(string[] args)
{
Console.WriteLine("End");
Console.ReadLine();
}
LazyInitializer
原理是,当有多个线程同时创建对象时,直接取第一个创建的对象,多余的对象就不管了。速度很快,但是相应的浪费了CPU
A _a;
public A a
{
get
{
LazyInitializer.EnsureInitialized(ref _a, () => new A());
return _a;
}
}
线程本地存储
使线程有各自的全局变量
ThreadStatic
不好用
[ThreadStatic]
static int num = 5;
static void Main(string[] args)
{
new Thread(Go).Start(); //6
new Thread(Go).Start(); //1
new Thread(Go).Start(); //1
new Thread(Go).Start(); //1
Console.WriteLine(num); //0
Console.WriteLine("End");
Console.ReadLine();
}
static void Go()
{
num++;
Console.WriteLine(num);
}
ThreadLocal<T>
好用,和预想的一样
static ThreadLocal<int> num = new ThreadLocal<int>(() => 3);
static void Main(string[] args)
{
new Thread(Go).Start();
new Thread(Go).Start();
new Thread(Go).Start();
new Thread(Go).Start();
Console.WriteLine(num);
Console.WriteLine("End");
Console.ReadLine();
}
static void Go()
{
num.Value++;
Console.WriteLine(num.Value);
}
LocalDataStoreSlot
不怎么好用
static LocalDataStoreSlot _slot = Thread.GetNamedDataSlot("Secure");
static int num
{
get
{
object data = Thread.GetData(_slot);
return data == null ? 0 : (int)data;
}
set
{
Thread.SetData(_slot, value);
}
}
Interrupt和Abort方法
可以终止别的线程运行
定时器
多线程定时器
2种都是多线程
static void Main(string[] args)
{
//参数:方法,参数,延迟,循环的时间间隔
Timer timer = new Timer(Go, "timeeee", 2000, 1000);
Console.WriteLine("End");
Thread.Sleep(7000);
//中断
timer.Dispose();
Console.ReadLine();
}
static void Go(object state)
{
Console.WriteLine(state);
}
只调用一次,将时间间隔设置为Timeout.Infinite,其实就是-1
static void Main(string[] args)
{
Timer timer = new Timer();
timer.Interval = 500;
timer.Elapsed += Go;
timer.Start();
timer.Stop();
Console.ReadLine();
}
static void Go(object state, EventArgs arg)
{
Console.WriteLine(state);
}