diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs index 40c14ae102c418..c2e69c5dfdff09 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs @@ -75,14 +75,12 @@ private static void CreateWorkerThread() Thread workerThread = new Thread(s_workerThreadStart); workerThread.IsThreadPoolThread = true; workerThread.IsBackground = true; - // thread name will be set in thread proc + workerThread.SetThreadPoolWorkerThreadName(); workerThread.UnsafeStart(); } private static void WorkerThreadStart() { - Thread.CurrentThread.SetThreadPoolWorkerThreadName(); - PortableThreadPool threadPoolInstance = ThreadPoolInstance; if (NativeRuntimeEventSource.Log.IsEnabled()) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs index ed4aaf75758075..0f7b2fc570a39f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs @@ -403,7 +403,7 @@ public string? Name internal void SetThreadPoolWorkerThreadName() { - Debug.Assert(this == CurrentThread); + Debug.Assert(ThreadState.HasFlag(ThreadState.Unstarted) || this == CurrentThread); Debug.Assert(IsThreadPoolThread); lock (this) diff --git a/src/libraries/System.Threading.ThreadPool/tests/ThreadPoolTests.cs b/src/libraries/System.Threading.ThreadPool/tests/ThreadPoolTests.cs index a26b000055a7f7..a0b09a7f279062 100644 --- a/src/libraries/System.Threading.ThreadPool/tests/ThreadPoolTests.cs +++ b/src/libraries/System.Threading.ThreadPool/tests/ThreadPoolTests.cs @@ -1467,6 +1467,68 @@ public static unsafe void ThreadPoolCompletedWorkItemCountTest() }).Dispose(); } + [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported))] + public static void ThreadPoolThreadCreationDoesNotCauseLockContention() + { + // Run in a separate process to test in a clean thread pool environment such that work items queued by the test + // would cause the thread pool to create threads + RemoteExecutor.Invoke(() => + { + int processorCount = Environment.ProcessorCount; + var values = new double[processorCount]; + StartBusyThreads(); + + long lockContentionCount = Monitor.LockContentionCount; + var done = new ManualResetEvent(false); + int count = 0; + + for (int i = 0; i < processorCount; i++) + { + ThreadPool.QueueUserWorkItem(m => + { + if (Interlocked.Increment(ref count) == processorCount) + { + done.Set(); + } + }); + } + + done.WaitOne(); + + Assert.Equal(0, Monitor.LockContentionCount - lockContentionCount); + + void StartBusyThreads() + { + using var sem = new Semaphore(0, values.Length); + + for (int i = 0; i < values.Length; i++) + { + int index = i; + + var t = new Thread(_ => + { + sem.Release(); + + while (true) + { + for (int j = 0; j < int.MaxValue; j++) + { + values[index] += Math.Sqrt(j); + } + } + }); + + t.Start(); + } + + for (int i = 0; i < values.Length; i++) + { + sem.WaitOne(); + } + } + }).Dispose(); + } + public static bool IsThreadingAndRemoteExecutorSupported => PlatformDetection.IsThreadingSupported && RemoteExecutor.IsSupported;