线程安全与锁

线程安全

线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。

多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。

线程安全问题往往都是由全局变量及静态变量引起的。

若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

 

C#中的lock

在C#语言中,开发人员可以使用 lock 语句确保线程安全。lock 语句获取给定对象的互斥 lock,执行语句块,然后释放 lock。持有 lock 时,持有 lock 的线程可以再次获取并释放 lock。阻止任何其他线程获取 lock 并等待释放lock。在lock语句块中不能使用await关键字。语法如下。

lock (x)
{
    // Your code...
}

 

对实例内的共享变量,要实现该变量的同步线程访问,可以锁定专用的对象实例(private readonly object  balanceLock = new object())

Account 类,该类通过锁定专用的 balanceLock 实例来同步对其专用 balance 字段的访问。 使用相同的实例进行锁定可确保尝试同时调用 Debit 或 Credit 方法的两个线程无法同时更新 balance 字段。

using System;
using System.Threading.Tasks;

public class Account
{
    private readonly object balanceLock = new object();
    private decimal balance;

    public Account(decimal initialBalance)
    {
        balance = initialBalance;
    }

    public decimal Debit(decimal amount)
    {
        lock (balanceLock)
        {
            if (balance >= amount)
            {
                Console.WriteLine($"Balance before debit :{balance, 5}");
                Console.WriteLine($"Amount to remove     :{amount, 5}");
                balance = balance - amount;
                Console.WriteLine($"Balance after debit  :{balance, 5}");
                return amount;
            }
            else
            {
                return 0;
            }
        }
    }

    public void Credit(decimal amount)
    {
        lock (balanceLock)
        {
            Console.WriteLine($"Balance before credit:{balance, 5}");
            Console.WriteLine($"Amount to add        :{amount, 5}");
            balance = balance + amount;
            Console.WriteLine($"Balance after credit :{balance, 5}");
        }
    }
}

class AccountTest
{
    static void Main()
    {
        var account = new Account(1000);
        var tasks = new Task[100];
        for (int i = 0; i < tasks.Length; i++)
        {
            tasks[i] = Task.Run(() => RandomlyUpdate(account));
        }
        Task.WaitAll(tasks);
    }

    static void RandomlyUpdate(Account account)
    {
        var rnd = new Random();
        for (int i = 0; i < 10; i++)
        {
            var amount = rnd.Next(1, 100);
            bool doCredit = rnd.NextDouble() < 0.5;
            if (doCredit)
            {
                account.Credit(amount);
            }
            else
            {
                account.Debit(amount);
            }
        }
    }
}
View Code

private ,设为私有的以确保不被实例外部访问, 避免对不同的共享资源使用相同的 lock 对象实例,因为这可能导致死锁或锁争用。

readonly,确保balancLock对象不会被修改,若balancLock被修改,其他线程也会被放进来。

 

对某个类的多个实例之间的共享变量(资源),若要实现该变量(资源)的同步线程访问,可以锁定静态字段对象实例(private static  readonly object  writeLock = new object())

class Program
    {
        static void Main(string[] args)
        {
            List<Log> logs = new List<Log>(){/*new10次*/ };
            for(int i=0;i<10;i++)
            {
                Thread thread = new Thread(new ThreadStart(logs[i].wirteLog));
                thread.Start();
            }
        }
    }

    class Log
    {
        private static readonly object wirteLock = new object();
        public void wirteLog()
        {
            lock (wirteLock)
            {
                FileInfo txt = new FileInfo("log.txt");
                FileStream stream= txt.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read);
                byte[] buffer = Encoding.UTF8.GetBytes("xxx");
                stream.Seek(stream.Length,SeekOrigin.Begin);
                stream.Write(buffer, 0, buffer.Length);
                stream.Close();
                stream.Dispose();
            }

        }
    }
View Code

 

 

参考引用

https://baike.baidu.com/item/%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8

https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/lock-statement

 

posted @ 2019-01-05 17:15  雄介  阅读(294)  评论(0编辑  收藏  举报