我的问题是如何解决并发问题,不要使用lock
案例
void Main()
{
Enumerable.Range(0,20).ToList().ForEach(i=>{
MockMulit();
});
}
//Start a certain number of threads for concurrent simulation
void MockMulit()
{
int threadCount=100;
ClearData();//delete all data for test
var tasks=new List<Task>(threadCount);
Enumerable.Range(1,threadCount).ToList().ForEach(i=>{
var j=i;
tasks.Add(Task.Factory.StartNew(()=>T3(string.Format("Thread{0}-{1}",Thread.CurrentThread.ManagedThreadId,j))));
});
Task.WaitAll(tasks.ToArray());
CountData().Dump();//show that the result
}
方法一 - 并发非常严重
void T1(string name)
{
using(var conn=GetOpendConn())
{
var count=conn.Query<int>(@"select count(*) from dbo.Down").Single();
if(count<20)
{
conn.Execute(@"insert into dbo.Down (UserName) values (@UserName)",new{UserName=name});
}
}
}
方法二 - 把sql放在一起可以减少并发,但仍然存在
void T2(string name)
{
using(var conn=GetOpendConn())
{
conn.Execute(@"
if((select count(*) from dbo.Down)<20)
begin
--WAITFOR DELAY '00:00:00.100';
insert into dbo.Down (UserName) values (@UserName)
end",new{UserName=name});
}
}
方法三 - 用lock销毁并发,但我认为这不是最佳解决方案
private static readonly object countLock=new object();
void T3(string name)
{
lock(countLock)
{
using(var conn=GetOpendConn())
{
var count=conn.Query<int>(@"select count(*) from dbo.Down").Single();
if(count<20)
conn.Execute(@"insert into dbo.Down (UserName) values (@UserName)",new{UserName=name});
}
}
}
其他帮助方法
//delete all data
void ClearData()
{
using(var conn=GetOpendConn())
{
conn.Execute(@"delete from dbo.Down");
}
}
//get count
int CountData()
{
using(var conn=GetOpendConn())
{
return conn.Query<int>(@"select count(*) from dbo.Down").Single();
}
}
//get the opened connection
DbConnection GetOpendConn()
{
var conn=new SqlConnection(@"Data Source=.;Integrated Security=SSPI;Initial Catalog=TestDemo;");
if(conn.State!=ConnectionState.Open)
conn.Open();
return conn;
}
答案 0 :(得分:4)
听起来你只想在少于20行时插入Down。如果是这样:进行一次操作:
insert dbo.Down (UserName)
select @UserName
where (select count(1) from dbo.Down) < 20
select @@rowount -- 1 if we inserted, 0 otherwise
或者,如果您* 需要 *,您将需要使用一个事务,理想情况下是“可序列化”,这样您就可以获得一个键范围锁定 - 甚至可以添加(UPDLOCK)
初始计数,以确保它需要一个急切的写锁(或块,而不是死锁)。但是:单一的TSQL操作(如已经说明的那样更好。你甚至可以让那更偏执(虽然我不确定它需要它):
declare @count int
begin tran
insert dbo.Down (UserName)
select @UserName
where (select count(1) from dbo.Down (UPDLOCK)) < 20
set @count = @@rowount
commit tran
select @count -- 1 if we inserted, 0 otherwise
答案 1 :(得分:1)
考虑反过来这样做,它可能会简单得多。例如:
如果您的数据库支持多列主键,其中一个主键自动递增(IIRC,MyISAM表),您可以在多个产品特价中重复使用“Up”表。
更简单的实现是在“向下”中插入20行并在每个请求中删除一行。检查受影响的行数(应为1)以查看用户是否成功。这与多产品特价很好地搭配。例如:
delete * from Down where down_special_id = Xxx limit 1
可能最简单,并且还要跟踪谁“赢了”,为每个产品创建一行并让每个用户更新一行(并且只有一行)。再次,检查受影响的行数以查看它们是否成功:
update Up
set
user_name = @user_name
where
user_name is null
and product_id = @product_id
limit 1