.NET多线程

.NET多线程(六)并发同步

2017-03-12  本文已影响0人  万州大牛

共享资源

说简单点,就是同一个实例,或同一个变量,被多个任务所拥有。

资源竞争

一个东西被多个任务拥有,谁先谁后处理,这就是资源竞争。

关键区域

指资源竞争发生的代码区块,这些代码区块是需要同步处理的关键。
这些代码区块不仅限于方法里的语句,也包含其他方法对共享资源的操作。
简单理解就是,方法1对共享A操作,方法2也对共享A操作,此时方法1和方法2就需要一个协调者(同步构造),进行同步处理。同理,推广至,类,文件等共享资源。

同步构造

4种类型

(1)原子操作(自增)
(2)内核模式(阻塞,上下文切换)
(3)用户模式(自旋)
(4)混合模式(用户+内核)
不理解没关系,后续详解

关键点

是否选择了正确的同步构造?
是否正确使用了选择的同步构造?

建议

尽量不同步(调整代码业务优先考虑),别同步太多,别同步太少,选择最廉价的同步构造,别自己写同步构造。

6、并发同步

6.1 并发同步基础

异步带来了以下问题

# 竞争条件 race conditions
# 死锁 deadlocks
# 数据损坏 data corruption

所有问题的核心是:数据

本节主要内容,

** 数据及状态转换,同步构造 Interlocked,MySpinLock,ReaderWriterLock ,ReaderWriterLockSlim
**

(1)数据及状态转换

共享的数据,多线程
只读的,不变状态的数据

原子状态转换
这通常是指1条CPU指令,32位就是32位类型,64位就是64位
32位的double就不是原子操作

非原子状态转换
多线程读写,可能读到错误的数据,因为写可能才写一半
也可能读写虽然都是原子性,但是读到的都是写之前的数据

【代码1】
static void Main(string[] args)
{
    int taskCount = 500;
    int count = 50;
    int result = 0;
    List<Task> tasks = new List<Task>();
    for (int i = 0; i < taskCount; i++)
    {
        Task t = Task.Factory.StartNew(() =>
        {
            for (int j = 0; j < count; j++)
            {
                result++;
            }
        });
        tasks.Add(t);
    }
    Task.WaitAll(tasks.ToArray());
    Console.WriteLine(result);
    Console.WriteLine(string.Format("期望的正确结果:{0}", taskCount * count));
    Console.ReadLine();
}

分析 result++; 操作
(1)先把内存变量值拷贝到CPU寄存器
(2)CPU计算对寄存器执行+1
(3)在从寄存器把值拷贝到内存变量

结果很明显

如果2个线程
第1个线程执行步骤2寄存器+1之后
第2个线程执行步骤1拷贝内存变量值到寄存器
这时候,第1个线程再执行步骤3从寄存器读到数据就是错误的了

(2)CPU 缓存,比从内存读数据快

L1一级缓存
L2二级缓存
甚至L3三级缓存

(3)异步,并发同步

使用异步的目的是为了并发处理,提高性能
但异步带来的问题,共享数据因多线程竞争条件可能导致数据损坏
解决问题,就是对共享资源做并发同步处理,但这时候并发性能就会降低

同步构造

System.Threading.Interlocked

适用场景,同步单个字段,属性,比如计数器,需要多任务更新计数

# Interlocked.Increment
将【代码1】中
result++;
替换为
System.Threading.Interlocked.Increment(ref result);
# 使用这个同步构造后,执行时间会延长,但是不做同步处理,数据就会损坏
# Interlocked.Decrement
# Interlocked.Add
# Interlocked.Exchange
用来打造轻量级同步锁
// 例如 SpinLock,SpinLock 适用高并发短时操作
// .NET 已经提供 System.Threading.SpinLock,下面是我的 SpinLock 实现
public struct MySpinLock
{
    private int locked;
    public void Lock()
    {
        // 这里获取锁,如果locked=1,说明被其他线程锁住了,这时候就 while 循环 Spin 等待
        while (Interlocked.Exchange(ref locked, 1) == 1) { }
    }
    public void Unlock()
    {
        locked = 0;
    }
}
static void Main(string[] args)  // .NET提供的SpinLock 
{
    System.Threading.SpinLock spinLock = new System.Threading.SpinLock();
    int taskCount = 500;
    int count = 50;
    int result = 0;
    List<Task> tasks = new List<Task>();
    for (int i = 0; i < taskCount; i++)
    {
        Task t = Task.Factory.StartNew(() =>
        {
            bool lockTaken = false;
            spinLock.Enter(ref lockTaken);
            for (int j = 0; j < count; j++)
            {
                result++;
            }
            spinLock.Exit();
        });
        tasks.Add(t);
    }
    Task.WaitAll(tasks.ToArray());
    Console.WriteLine(result);
    Console.WriteLine(string.Format("期望的正确结果:{0}", taskCount * count));
    Console.ReadLine();
}
# Interlocked.CompareExchange
用来打造单例模式
public class MySingleton
{
    private MySingleton() { }
    private static MySingleton instance;
    public static MySingleton Instance
    {
        get
        {
            if (instance == null)
            {
                // instance 和 null 比较,相等就 new
                return Interlocked.CompareExchange(ref instance, new MySingleton(), null);
            }
            return instance;
        }
    }
}

Mutex

适用场景,跨进程唯一,比如通常有需要保证相同exe只运行一个的需求。

(3)System.Threading.ReaderWriterLock 和 System.Threading.ReaderWriterLockSlim

适用:读线程多,写线程少

思想,因为并发读没有线程安全问题
我们可以让读同时进行,然后锁住,让写单独进行

ReaderWriterLock

当获取了读的锁,在执行时,发现需要进行写操作怎么办?
可以调用 UpgradeToWriteLock 方法升级为写锁

ReaderWriterLock 有2个问题,比较慢,还可能造成写饥饿
意思就是:
当所有的读操作获取锁,并且不断的读操作持续请求,
那么,写操作就会一直等

所以引入 ReaderWriterLockSlim
提高速度的同时,在写操作之后,对后续的读操作进行了排队,
这样写操作,就不会饥饿,一直获取不到锁

private List<NewsItem> newsList = new List<NewsItem>();
private ReaderWriterLockSlim lockSlim = new ReaderWriterLockSlim();
public IEnumerable<NewsItem> GetNews(string tag)
{
    lockSlim.EnterReadLock();
    try
    {
        return newsList.Where((news) => { return news.Tag == tag; }).ToList();
    }
    finally
    {
        lockSlim.ExitReadLock();
    }
}
public void AddNews(NewsItem newsItem)
{
    lockSlim.EnterWriteLock();
    try
    {
        newsList.Add(newsItem);
    }
    finally
    {
        lockSlim.ExitWriteLock();
    }
}
上一篇下一篇

猜你喜欢

热点阅读