.net中lock的应用

一般方法与webapi

Posted by BY on October 10, 2023

C#中,多线程代码会共享栈区等变量资源,新建一个控制台程序,demo程序如下:

using System;
using System.Threading.Tasks;

namespace ConsoleApp3
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Test t = new Test();
            Task.Run(() => t.Increase());
            Task.Run(() => t.Increase());
            Console.ReadLine();
        }
    }

    public class Test
    {
        public int counter = 0;
        public void Increase()
        {
            int i;
            for (i = 0; i < 1000000000; i++)
            {
                counter++;
            }
            Console.WriteLine("This counter is " + counter);
            Console.WriteLine("i is " + i);
        }
    }
}

上面的代码中,Increase方法里面的循环次数k越多,得到的counter离2k就越大,当k为1、10、100、1000甚至10000时,由于第二个线程尚未进入Increase方法,第一个线程就已经执行完毕了,因此counter会等于2k。但是当循环次数比较大时,如第一个线程读出counter为100000,第二个线程此时也读出counter为100000,两者同时相加,得出counter=100001,但是此时在两个线程已经分别进行过一次循环了,因此,循环次数越大,两个线程“碰撞”读取的概率也就越大,最终得到的counter离2k差距就越大。(两个线程并不会共享方法内的变量i,因此两个线程合计循环必然是2k次)

为了解决这个问题,可以在读取counter时加上locker,demo程序如下:

using System;
using System.Threading.Tasks;

namespace ConsoleApp3
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Test t = new Test();
            Task.Run(() => t.Increase());
            Task.Run(() => t.Increase());
            Console.ReadLine();
        }
    }

    public class Test
    {
        public int counter = 0;
        private static object locker = new();
        public void Increase()
        {
            int i;
            for (i = 0; i < 10000000; i++)
            {
                lock(locker)
                {
                    counter++;
                }
            }
            Console.WriteLine("This counter is " + counter);
            Console.WriteLine("i is " + i);
        }
    }
}

输出的结果大致如下:

This counter is 19991398
i is 10000000
This counter is 20000000
i is 10000000

最后一个线程输出的counter必然为2k,因为当第一个线程读取并操作i时,另外一个线程无法进入,只能在外面等待,就不会有重复读的问题,相应的,整个程序的执行时间也会变长。

locker放在increase方法中的任意位置均有效

using System;
using System.Threading.Tasks;

namespace ConsoleApp3
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Test t = new Test();
            Task.Run(() => t.Increase());
            Task.Run(() => t.Increase());
            Console.ReadLine();
        }
    }

    public class Test
    {
        public int counter = 0;
        private static object locker = new();
        public void Increase()
        {
            lock (locker)
            {
                int i;
                for (i = 0; i < 99999999; i++)
                {
                    counter++;
                }
            }
            Console.WriteLine("This counter is " + counter);
        }
    }
}

当然效果会有所不同,当lock放在for循环外面时,会先执行完线程1,即i=0,,,,99999之后再线程20,,,,99999,但是但lock放在for循环里面时,线程1和线程2会交叉执行,i=0,,,,99999,99999,锁住的只是counter。

lock在webapi中的应用

前端打开两个界面,同时请求一个api

using Microsoft.AspNetCore.Mvc;

namespace WebApplication4.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

        private readonly ILogger<WeatherForecastController> _logger;
        private static readonly object _locker = new object();

        public WeatherForecastController(ILogger<WeatherForecastController> logger)
        {
            _logger = logger;
        }

        [HttpGet(Name = "GetWeatherForecast")]
        public IEnumerable<WeatherForecast> Get()
        {
            lock(_locker)
            {
                Thread.Sleep(10000);
                return Enumerable.Range(1, 5).Select(index => new WeatherForecast
                {
                    Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
                    TemperatureC = Random.Shared.Next(-20, 55),
                    Summary = Summaries[Random.Shared.Next(Summaries.Length)]
                })
     .ToArray();
            }
        }
    }
}

前一个api用时大概在10s,后一个api用时接近20s。注意:这里的locker不加static是不起作用的

tip1: .net解决跨域

app.UseCors(builder =>
        builder
        .WithOrigins("*")
        .AllowAnyMethod()
        .AllowAnyHeader());