C# 线程同步(二)

多个线程同时共享对象会造成很多问题。同步这些线程使得对共享对象的操作能以正确的顺序执行非常重要。

竞争问题是多线程执行没有正确同步。

多个线程之间共享对象资源,存在一个竞争,需要正确的取得资源,就需要线程同步。原子操作:一个操作只占一个量子的时间,一次就可以完成,只有当前操作完成后,其它线程才能被执行其它操作。

方式1:将线程置于阻塞状态

线程阻塞时,只会占用尽可能少的CPU时间,这样至少要一次上下文切换。如果线程要挂起很长时间,这么做时值得的。这种方式又称内核模式- Kernel mode。只有操作系统的内核才能阻止线程使用CPU时间

方式2:简单的等待

用户模式:user-mode, 线程只需等待一小段时间。该方式非常轻量,速度快,如果等待时间长,则浪费大量CPU时间。

方式3:混合模式

hybrid, 混合模式先尝试使用用户模式等待,如果线程等待了足够长的时间,则切换到阻塞状态以节省CPU资源

1. 执行基本的原子操作

不用阻塞线程就可避免竞争条件

测试验证代码如下:

class Program

{

abstract class CounterBase

{

public abstract void Increment();

public abstract void Descrement();

}

static void TestCounter(CounterBase c)

{

for (int i = 0; i < 100000; i++)

{

c.Increment();

c.Descrement();

}

}

//不使用Lock

class Counter : CounterBase

{

private int _count;

public int Count { get { return _count; } }

public override void Descrement()

{

//Thread.Sleep(1);

_count–;

}

public override void Increment()

{

_count++;

}

}

//使用原子操作

class CounterWithNoLock : CounterBase

{

private int _count;

public int Count

{

get

{

return _count;

}

}

public override void Descrement()

{

Interlocked.Decrement(ref _count);

}

public override void Increment()

{

Interlocked.Increment(ref _count);

}

}

static void Main(string[] args)

{

Console.WriteLine(“Incorrect Counter”);

var c = new Counter();

var t1 = new Thread(() => TestCounter©);

var t2 = new Thread(() => TestCounter©);

var t3 = new Thread(() => TestCounter©);

t1.Start();

t2.Start();

t3.Start();

t1.Join();

t2.Join();

t3.Join();

Console.WriteLine($“Total count:{c.Count}”);

Console.WriteLine("-----------------------");

Console.WriteLine(“Correct Counter”);

var c1 = new CounterWithNoLock();

t1 = new Thread(() => TestCounter(c1));

t2 = new Thread(() => TestCounter(c1));

t3 = new Thread(() => TestCounter(c1));

t1.Start();

t2.Start();

t3.Start();

t1.Join();

t2.Join();

t3.Join();

Console.WriteLine($“Total count:{c1.Count}”);

}

}

输出:

Incorrect Counter

Total count:-2554

-----------------------

Correct Counter

Total count:0

创建3个线程来执行TestCounter方法,第一个测试代码,线程不是安全的,存在竞争,所以计数的结果不是预期的0,有可能是0,但这个计数的结果是不确定的。第二个测试代码,使用原子锁,一定会得到预期的值0。

2.使用Mutex类

一种原始的同步方式,其只对一个线程授予对共享资源的独占访问

class Program

{

//主程序定义一个指定名称的互斥量

const string MutexName = “ThreadingCookBook”;

static void Main(string[] args)

{

//设置intialOwner标志为false,意味如果互斥量已经被删除,则允许程序获取该互斥量

//程序如果没有获取到互斥量,程序则简单的显示为Running,等按下任意键,释放该互斥量

using (var m = new Mutex(false, MutexName)) //互斥量是全局的操作系统对象,最佳方式是使用using代码块来包裹

{

if (!m.WaitOne(TimeSpan.FromSeconds(5), false))

{

Console.WriteLine(“Second instance is running!”);

}

else

{

Console.WriteLine(“Running!”);

Console.ReadLine();

m.ReleaseMutex();

}

//再运行同样程序,则会再5秒内尝试获取互斥量

//如果此时在第一个程序中按下了键第二个程序则会开始执行。

//如果保持5秒,则第二个程序无法获取该互斥量

}

Console.Read();

}

}

互斥量是全局的操作系统对象,必须确保正确关闭。最佳方式使用using代码块。

3. 使用SemaphoreSlim类

该类限制了同时访问一个资源的线程数量

class Program

{

static SemaphoreSlim _semaphore = new SemaphoreSlim(4);

static void AccessDatabase(string name,int seconds)

{

Console.WriteLine($"{name} waits to access a database");

_semaphore.Wait();

Console.WriteLine($"{name} was granted an access to a database");

Thread.Sleep(TimeSpan.FromSeconds(seconds));

Console.WriteLine($"{name} is completed");

_semaphore.Release();

}

static void Main(string[] args)

{

for (int i = 0; i <= 6; i++)

{

string thName = "Thread " + i;

int secondsToWait = 2 + 2 * i;

var t = new Thread(() => AccessDatabase(thName, secondsToWait));

t.Start();

}

}

}

创建SemaphoreSlim 实例,构造对象时指定并发线程数量4.然后启动6个不同线程。借助信号系统显示访问数据库的并发线程为4个,所以有2个线线程需要等待,直到之前线程种的某一个完成工作, Release信号。

4.AutoResetEvent类

从一个线程向另一个线程发送通知

class Program

{

private static AutoResetEvent _workerEvent = new AutoResetEvent(false);

private static AutoResetEvent _mainEvent = new AutoResetEvent(false);

static void Process(int seconds)

{

Console.WriteLine($“Starting a long running work…”);

Thread.Sleep(TimeSpan.FromSeconds(seconds));

Console.WriteLine(“Work is done”);

_workerEvent.Set();

Console.WriteLine(“Waiting for a main thread to complete its work”);

_mainEvent.WaitOne();

Console.WriteLine(“Starting second operation…”);

Thread.Sleep(TimeSpan.FromSeconds(seconds));

Console.WriteLine(“Work is done”);

_workerEvent.Set();

}

static void Main(string[] args)

{

var t = new Thread(() => Process(10));

t.Start();

Console.WriteLine(“Waiting for another thread to complete work”);

_workerEvent.WaitOne();

Console.WriteLine(“First operation is completed!”);

Console.WriteLine(“Performing an operation on a main thread”);

Thread.Sleep(TimeSpan.FromSeconds(5));

_mainEvent.Set();

Console.WriteLine(“Now running the second operation on a second thread”);

_workerEvent.WaitOne();

Console.WriteLine(“Second operation is completed”);

}

}

5.使用ManualResetEventSlim类

在线程之间以更加灵活的方法传递信号

class Program

{

static ManualResetEventSlim _mainEvent = new ManualResetEventSlim(false);

static void TravelThroughGates(string threadName, int seconds)

{

Console.WriteLine($"{threadName} falls to sleep");

Thread.Sleep(TimeSpan.FromSeconds(seconds));

Console.WriteLine($"{threadName} waits for the gates to open");

_mainEvent.Wait();

Console.WriteLine($"{threadName} enters the gates!");

}

static void Main(string[] args)

{

var t1 = new Thread(() => TravelThroughGates(“Thread 1”, 5));

var t2 = new Thread(() => TravelThroughGates(“Thread 2”, 6));

var t3 = new Thread(() => TravelThroughGates(“Thread 3”, 12));

t1.Start();

t2.Start();

t3.Start();

Thread.Sleep(TimeSpan.FromSeconds(6));

Console.WriteLine(“The gates are now open”);

_mainEvent.Set();

Thread.Sleep(TimeSpan.FromSeconds(2));

_mainEvent.Reset();

Console.WriteLine(“The gates have been closed!”);

Thread.Sleep(TimeSpan.FromSeconds(10));

Console.WriteLine(“The gates are now open for the second time”);

_mainEvent.Set();

Thread.Sleep(TimeSpan.FromSeconds(2));

Console.WriteLine(“The gates have been colsed!”);

_mainEvent.Reset();

}

}

6. 使用CountDowenEvent类

使用CountdownEvent信号类来等待直到一定数量的操作完成

class Program

{

static CountdownEvent _countdown = new CountdownEvent(2);

static void PerformOperation(string message, int seconds)

{

Thread.Sleep(TimeSpan.FromSeconds(seconds));

Console.WriteLine(message);

_countdown.Signal();

}

static void Main(string[] args)

{

Console.WriteLine(“Starting two operation”);

var t1 = new Thread(() => PerformOperation(“Operation 1 is completed”, 4));

var t2 = new Thread(() => PerformOperation(“Operation 2 is completed”, 8));

t1.Start();

t2.Start();

_countdown.Wait();

Console.WriteLine(“Both operations have been completed!”);

_countdown.Dispose();

}

}

7. 使用Barrier类

用于组织多个线程及时在某个时刻碰面

class Program

{

static Barrier _brrier = new Barrier(2, b => Console.WriteLine($“End of phase {b.CurrentPhaseNumber + 1}”));

static void PlayMusic(string name, string message, int seconds)

{

for (int i = 0; i < 3; i++)

{

Console.WriteLine("-----------------------------------");

Thread.Sleep(TimeSpan.FromSeconds(seconds));

Console.WriteLine($"{name} starts to {message}");

Thread.Sleep(TimeSpan.FromSeconds(seconds));

Console.WriteLine($"{name} finishes to {message}");

_brrier.SignalAndWait();

}

}

static void Main(string[] args)

{

var t1 = new Thread(() => PlayMusic(“The guitarist”, “Play an amazing solo”, 5));

var t2 = new Thread(() => PlayMusic(“The singer”, “Sing his song”, 2));

t1.Start();

t2.Start();

}

}

8. 使用ReaderWriterLockSlim类

创建一个线程安全的机制,在多线程中对一个集合进行读写操作。

class Program

{

static ReaderWriterLockSlim _rw = new ReaderWriterLockSlim();

static Dictionary<int, int> _items = new Dictionary<int, int>();

static void Read()

{

Console.WriteLine(“Reading contents of a dictionary”);

while (true)

{

try

{

_rw.EnterReadLock();

foreach (var key in _items.Keys)

{

Thread.Sleep(TimeSpan.FromSeconds(0.1));

}

}

finally

{

_rw.ExitReadLock();

}

}

}

static void Write(string threadName)

{

while (true)

{

try

{

int newKey = new Random().Next(250);

_rw.EnterUpgradeableReadLock();

if (!_items.ContainsKey(newKey))

{

try

{

_rw.EnterWriteLock();

_items[newKey] = 1;

Console.WriteLine($“New Key {newKey} is added to a dictionary by a {threadName}”);

}

finally

{

_rw.ExitReadLock();

}

}

Thread.Sleep(TimeSpan.FromSeconds(0.1));

}

finally

{

_rw.ExitUpgradeableReadLock();

}

}

}

static void Main(string[] args)

{

new Thread(Read) { IsBackground = true }.Start();

new Thread(Read) { IsBackground = true }.Start();

new Thread(Read) { IsBackground = true }.Start();

new Thread(() => Write(“Thread 1”)) { IsBackground = true }.Start();

new Thread(() => Write(“Thread 2”)) { IsBackground = true }.Start();

Thread.Sleep(TimeSpan.FromSeconds(30));

}

}

9. 使用SpinWait类

不使用内核模型的方式来使线程等待

class Program

{

static volatile bool _isCompleted = false;

static void UserModeWait()

{

while (!_isCompleted)

{

Console.WriteLine(".");

}

Console.WriteLine();

Console.WriteLine(“Waiting  is complete”);

}

static void HybridSpinWait()

{

var w = new SpinWait();

while (!_isCompleted)

{

w.SpinOnce();

Console.WriteLine(w.NextSpinWillYield);

}

Console.WriteLine(“Wating is complete”);

}

static void Main(string[] args)

{

var t1 = new Thread(UserModeWait);

var t2 = new Thread(HybridSpinWait);

Console.WriteLine(“Running user mode waiting”);

t1.Start();

Thread.Sleep(20);

_isCompleted = true;

Thread.Sleep(1000);

_isCompleted = false;

Console.WriteLine(“Running hybrid SpinWait construct waiting”);

t2.Start();

Thread.Sleep(5);

_isCompleted = true;

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

flysh05

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值