Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make realization if the thread-safe priority queue #10

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions kr3/MyPriorityQueue.Tests/MyPriorityQueue.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Include="coverlet.collector" Version="3.1.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\MyPriorityQueue\MyPriorityQueue.csproj" />
</ItemGroup>

</Project>
112 changes: 112 additions & 0 deletions kr3/MyPriorityQueue.Tests/Test.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
namespace MyPriorityQueue.Tests;

using MyPriorityQueue;
using NUnit.Framework;
using System.Collections.Generic;
using System.Threading;

public class Tests
{
[Test]
public void EnqueueSizeAndDequeueFromOneThread()
{
var priorityQueue = new PriorityQueue<int>();
priorityQueue.Enqueue(1231, -1);
priorityQueue.Enqueue(1231, 1);
Assert.AreEqual(2, priorityQueue.Size);
priorityQueue.Enqueue(1, 23);
Assert.AreEqual(3, priorityQueue.Size);
Assert.AreEqual(1, priorityQueue.Dequeue());
Assert.AreEqual(2, priorityQueue.Size);
priorityQueue.Enqueue(12, 1);
Assert.AreEqual(3, priorityQueue.Size);
Assert.AreEqual(1231, priorityQueue.Dequeue());
Assert.AreEqual(2, priorityQueue.Size);
Assert.AreEqual(12, priorityQueue.Dequeue());
Assert.AreEqual(1, priorityQueue.Size);
Assert.AreEqual(1231, priorityQueue.Dequeue());
Assert.AreEqual(0, priorityQueue.Size);
}

[Test]
public void EnqueueFromDifferentThreads()
{
var priorityQueue = new PriorityQueue<int>();
var threads = new Thread[3];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new Thread в современном .NET не нужен, есть пул потоков и Task.Run (или более высокоуровневые вещи, типа Parallel.ForEach)

for (int i = 0; i < threads.Length; ++i)
{
var localI = i;
threads[i] = new Thread(() =>
{
priorityQueue.Enqueue(localI, 1);
});
}
foreach (var thread in threads)
{
thread.Start();
}
foreach (var thread in threads)
{
thread.Join();
}
Assert.AreEqual(3, priorityQueue.Size);
}

[Test]
public void DequeueFromDifferentThreadsAndSavingFIFO()
{
var priorityQueue = new PriorityQueue<string>();
priorityQueue.Enqueue("true", 1);
priorityQueue.Enqueue("false", 1);
priorityQueue.Enqueue("abobus", 1);
var values = new string[3];
var threads = new Thread[3];
for (int i = 0; i < threads.Length; ++i)
{
var localI = i;
threads[i] = new Thread(() =>
{
values[localI] = priorityQueue.Dequeue();
});
}
foreach (var thread in threads)
{
thread.Start();
}
foreach (var thread in threads)
{
thread.Join();
}
var correctValues = new string[3];
correctValues[0] = "true";
correctValues[1] = "false";
correctValues[2] = "abobus";
Assert.AreEqual(correctValues, values);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Это не должно проходить :) Dequeue делается в разных потоках, и кто из них будет первым, тот и запишет в свою ячейку взятое из очереди значение. То есть даже если очередь возвращает всё как надо, values могут быть перемешанными

Assert.AreEqual(0, priorityQueue.Size);
}

[Test]
public void EnqueueAndDequeueFromDifferentThreadsWithoutExceptions()
{
var priorityQueue = new PriorityQueue<string>();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вынесли бы создание очереди в SetUp


var threads = new Thread[2];
threads[0] = new Thread(() =>
{
priorityQueue.Dequeue();
});
threads[1] = new Thread(() =>
{
priorityQueue.Enqueue("ololo", 131);
});
foreach (var thread in threads)
{
thread.Start();
}
foreach (var thread in threads)
{
thread.Join();
}
Assert.AreEqual(0, priorityQueue.Size);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Всего два потока, всего один раз...

}
26 changes: 26 additions & 0 deletions kr3/MyPriorityQueue/IPriorityQueue.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace MyPriorityQueue;

/// <summary>
/// Interface of thread-safe priority queue
/// </summary>
/// <typeparam name="TValue">Type of value</typeparam>
public interface IPriorityQueue<TValue>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Интерфейс не просили, хотя сделать его — вряд ли ошибка :)

{
/// <summary>
/// Add value ant its priority to the priority queue
/// </summary>
/// <param name="value">Given value</param>
/// <param name="priority">Given priority</param>
void Enqueue(TValue value, int priority);

/// <summary>
/// Removes and returns the first founded value with the highest priority from the priority queue
/// </summary>
/// <returns>Value with the highest priority</returns>
TValue Dequeue();

/// <summary>
/// Returns the quantity of elements
/// </summary>
int Size { get; }
}
10 changes: 10 additions & 0 deletions kr3/MyPriorityQueue/MyPriorityQueue.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
31 changes: 31 additions & 0 deletions kr3/MyPriorityQueue/MyPriorityQueue.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.1.32210.238
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyPriorityQueue", "MyPriorityQueue.csproj", "{B1F6ADA8-0C8E-4016-912A-B0193040CA8D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyPriorityQueue.Tests", "..\MyPriorityQueue.Tests\MyPriorityQueue.Tests.csproj", "{EA3D469B-6AD6-451A-9F64-D2479A4702A9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B1F6ADA8-0C8E-4016-912A-B0193040CA8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B1F6ADA8-0C8E-4016-912A-B0193040CA8D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B1F6ADA8-0C8E-4016-912A-B0193040CA8D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B1F6ADA8-0C8E-4016-912A-B0193040CA8D}.Release|Any CPU.Build.0 = Release|Any CPU
{EA3D469B-6AD6-451A-9F64-D2479A4702A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EA3D469B-6AD6-451A-9F64-D2479A4702A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EA3D469B-6AD6-451A-9F64-D2479A4702A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EA3D469B-6AD6-451A-9F64-D2479A4702A9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {42B04315-5D4D-4FA7-9A03-E4C34222BD0B}
EndGlobalSection
EndGlobal
53 changes: 53 additions & 0 deletions kr3/MyPriorityQueue/PriorityQueue.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
namespace MyPriorityQueue;

using System.Collections.Generic;
using System.Linq;

/// <summary>
/// Realisation of thread-safe priority queue interface
/// </summary>
/// <typeparam name="TValue">Type of value</typeparam>
public class PriorityQueue<TValue> : IPriorityQueue<TValue>
{
private readonly SortedList<int, Queue<TValue>> elementsByPriority = new();
private int counter = 0;
private readonly object locker = new();

public void Enqueue(TValue value, int priority)
{
lock (locker)
{
if (!elementsByPriority.ContainsKey(priority))
{
elementsByPriority[priority] = new Queue<TValue>();
}
elementsByPriority[priority].Enqueue(value);
counter++;
Monitor.PulseAll(locker);
}
}

public TValue Dequeue()
{
lock (locker)
{
while (counter == 0)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Кажется, counter должен быть volatile-полем, потому что этот поток может ещё не увидеть изменений из 25-й строки и снова встанет на Wait, будет дедлок. В .NET memory model вроде есть что-то про то, что взятие/освобождение замка влечёт синхронизацию памяти, так что на практике, скорее всего, всё будет хорошо, но мне кажется, лучше избегать таких проблем явно

{
Monitor.Wait(locker);
}

var maxPriority = elementsByPriority.Keys.Last();
var element = elementsByPriority[maxPriority].Dequeue();

if (elementsByPriority[maxPriority].Count == 0)
{
elementsByPriority.Remove(maxPriority);
}
counter--;

return element;
}
}

public int Size => counter;
}
8 changes: 8 additions & 0 deletions kr3/MyPriorityQueue/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace MyPriorityQueue;

public static class Program
{
public static void Main(string[] args)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Это не нужно. Как метод Main в современном .NET, так и точка входа в проекте, который по сути является библиотекой

{
}
}