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());