并行性是否与数据表写入事务的锁有用
本文关键字:事务 有用 是否 数据表 并行性 | 更新日期: 2023-09-27 18:32:55
并行性是否有助于锁定对象的性能,它应该单线程运行,还是有其他技术?
我注意到在访问数据集并从多个线程添加行时,会抛出异常。 因此,我创建了一个"线程安全"版本,通过在更新行之前锁定表来添加行。 此实现有效,但在许多事务中显得很慢。
public partial class HaMmeRffl
{
public partial class PlayerStatsDataTable
{
public void AddPlayerStatsRow(int PlayerID, int Year, int StatEnum, int Value, DateTime Timestamp)
{
lock (TeamMemberData.Dataset.PlayerStats)
{
HaMmeRffl.PlayerStatsRow testrow = TeamMemberData.Dataset.PlayerStats.FindByPlayerIDYearStatEnum(PlayerID, Year, StatEnum);
if (testrow == null)
{
HaMmeRffl.PlayerStatsRow newRow = TeamMemberData.Dataset.PlayerStats.NewPlayerStatsRow();
newRow.PlayerID = PlayerID;
newRow.Year = Year;
newRow.StatEnum = StatEnum;
newRow.Value = Value;
newRow.Timestamp = Timestamp;
TeamMemberData.Dataset.PlayerStats.AddPlayerStatsRow(newRow);
}
else
{
testrow.Value = Value;
testrow.Timestamp = Timestamp;
}
}
}
}
}
现在我可以从多个线程安全地调用它,但它真的能给我买任何东西吗?我可以以不同的方式执行此操作以获得更好的性能吗? 例如,有没有办法使用System.Collections.Concurrent
命名空间来优化性能或任何其他方法?
此外,我在整个数据集更新后更新基础数据库,这需要很长时间。 这是否被视为 I/O 操作,并且值得在数据集(或一定数量的行(中更新每一行后通过更新它来使用并行处理。
更新
我写了一些代码来测试并发处理与顺序处理,这表明进行并发处理需要大约 30% 的时间,我应该在这里使用顺序处理。我认为这是因为数据库上的锁导致ConcurrentQueue
上的开销比并行处理的收益更昂贵。 这个结论是否正确,我是否可以采取任何措施来加快处理速度,或者我是否陷入了"您必须同步任何写入操作"Datatable
。
这是我的测试代码,在科学上可能不正确。 这是计时器和它们之间的呼叫。
dbTimer.Restart();
Queue<HaMmeRffl.PlayersRow.PlayerValue> addPlayerRow = InsertToPlayerQ(addUpdatePlayers);
Queue<HaMmeRffl.PlayerStatsRow.PlayerStatValue> addPlayerStatRow = InsertToPlayerStatQ(addUpdatePlayers);
UpdatePlayerStatsInDB(addPlayerRow, addPlayerStatRow);
dbTimer.Stop();
System.Diagnostics.Debug.Print("Writing to the dataset took {0} seconds single threaded", dbTimer.Elapsed.TotalSeconds);
dbTimer.Restart();
ConcurrentQueue<HaMmeRffl.PlayersRow.PlayerValue> addPlayerRows = InsertToPlayerQueue(addUpdatePlayers);
ConcurrentQueue<HaMmeRffl.PlayerStatsRow.PlayerStatValue> addPlayerStatRows = InsertToPlayerStatQueue(addUpdatePlayers);
UpdatePlayerStatsInDB(addPlayerRows, addPlayerStatRows);
dbTimer.Stop();
System.Diagnostics.Debug.Print("Writing to the dataset took {0} seconds concurrently", dbTimer.Elapsed.TotalSeconds);
在这两个示例中,我都以相同的方式添加到Queue
和单线程ConcurrentQueue
。 唯一的区别是插入到数据表中。 单线程方法插入如下:
private static void UpdatePlayerStatsInDB(Queue<HaMmeRffl.PlayersRow.PlayerValue> addPlayerRows, Queue<HaMmeRffl.PlayerStatsRow.PlayerStatValue> addPlayerStatRows)
{
try
{
HaMmeRffl.PlayersRow.PlayerValue row;
while (addPlayerRows.Count > 0)
{
row = addPlayerRows.Dequeue();
TeamMemberData.Dataset.Players.AddPlayersRow(
row.PlayerID, row.Name, row.PosEnum, row.DepthEnum,
row.TeamID, row.RosterTimestamp, row.DepthTimestamp,
row.Active, row.NewsUpdate);
}
}
catch (Exception)
{
TeamMemberData.Dataset.Players.RejectChanges();
}
try
{
HaMmeRffl.PlayerStatsRow.PlayerStatValue row;
while (addPlayerStatRows.Count > 0)
{
row = addPlayerStatRows.Dequeue();
TeamMemberData.Dataset.PlayerStats.AddUpdatePlayerStatsRow(
row.PlayerID, row.Year, row.StatEnum, row.Value, row.Timestamp);
}
}
catch (Exception)
{
TeamMemberData.Dataset.PlayerStats.RejectChanges();
}
TeamMemberData.Dataset.Players.AcceptChanges();
TeamMemberData.Dataset.PlayerStats.AcceptChanges();
}
并发添加如下
private static void UpdatePlayerStatsInDB(ConcurrentQueue<HaMmeRffl.PlayersRow.PlayerValue> addPlayerRows, ConcurrentQueue<HaMmeRffl.PlayerStatsRow.PlayerStatValue> addPlayerStatRows)
{
Action actionPlayer = () =>
{
HaMmeRffl.PlayersRow.PlayerValue row;
while (addPlayerRows.TryDequeue(out row))
{
TeamMemberData.Dataset.Players.AddPlayersRow(
row.PlayerID, row.Name, row.PosEnum, row.DepthEnum,
row.TeamID, row.RosterTimestamp, row.DepthTimestamp,
row.Active, row.NewsUpdate);
}
};
Action actionPlayerStat = () =>
{
HaMmeRffl.PlayerStatsRow.PlayerStatValue row;
while (addPlayerStatRows.TryDequeue(out row))
{
TeamMemberData.Dataset.PlayerStats.AddUpdatePlayerStatsRow(
row.PlayerID, row.Year, row.StatEnum, row.Value, row.Timestamp);
}
};
Action[] actions = new Action[Environment.ProcessorCount * 2];
for (int i = 0; i < Environment.ProcessorCount; i++)
{
actions[i * 2] = actionPlayer;
actions[i * 2 + 1] = actionPlayerStat;
}
try
{
// Start ProcessorCount concurrent consuming actions.
Parallel.Invoke(actions);
}
catch (Exception)
{
TeamMemberData.Dataset.Players.RejectChanges();
TeamMemberData.Dataset.PlayerStats.RejectChanges();
}
TeamMemberData.Dataset.Players.AcceptChanges();
TeamMemberData.Dataset.PlayerStats.AcceptChanges();
}
单线程的时间差为 4.6 秒,并行线程的时间差为 6.1 秒。调用。
Lock & 事务不利于并行性和性能。
1(尽量避免锁定:不同的线程是否需要更新数据集中的同一行?
2(最小化锁定时间。
对于数据库操作使用可以尝试批量更新未来 ADO.NET:http://msdn.microsoft.com/en-us/library/ms810297.aspx
多线程可以在一定程度上提供帮助,因为一旦数据跨越您的应用程序边界,您将开始等待I/O,在这里您可以进行异步处理,因为您的应用程序无法控制各种参数(资源访问,网络速度等(,这将提供更好的用户体验(如果UI应用程序(。
现在对于你的场景,你可能想使用某种生产者/消费者队列,一旦队列中有一行可用,另一个线程就会开始处理它,但这同样会在一定程度上起作用。