diff --git a/CMakeLists.txt b/CMakeLists.txt
index 59ad0054332..b21e56a0a50 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1844,7 +1844,6 @@ set_src(BASE GLOB_RECURSE src/base
   hash_ctxt.h
   hash_libtomcrypt.cpp
   hash_openssl.cpp
-  lock_scope.h
   log.cpp
   log.h
   logger.h
diff --git a/src/base/lock_scope.h b/src/base/lock_scope.h
deleted file mode 100644
index ce7ea63fc6a..00000000000
--- a/src/base/lock_scope.h
+++ /dev/null
@@ -1,24 +0,0 @@
-#ifndef BASE_LOCK_SCOPE_H
-#define BASE_LOCK_SCOPE_H
-
-#include "system.h"
-
-class SCOPED_CAPABILITY CLockScope
-{
-public:
-	CLockScope(LOCK Lock) ACQUIRE(Lock, m_Lock) :
-		m_Lock(Lock)
-	{
-		lock_wait(m_Lock);
-	}
-
-	~CLockScope() RELEASE() REQUIRES(m_Lock)
-	{
-		lock_unlock(m_Lock);
-	}
-
-private:
-	LOCK m_Lock;
-};
-
-#endif
diff --git a/src/base/system.cpp b/src/base/system.cpp
index d737a0998c7..181fbde5cf5 100644
--- a/src/base/system.cpp
+++ b/src/base/system.cpp
@@ -3,24 +3,22 @@
 #include <atomic>
 #include <cctype>
 #include <charconv>
+#include <chrono>
+#include <cinttypes>
 #include <cmath>
 #include <cstdarg>
 #include <cstdio>
 #include <cstring>
 #include <iterator> // std::size
+#include <mutex>
 #include <string_view>
 
 #include "system.h"
 
-#include "lock_scope.h"
 #include "logger.h"
 
 #include <sys/types.h>
 
-#include <chrono>
-
-#include <cinttypes>
-
 #if defined(CONF_WEBSOCKETS)
 #include <engine/shared/websockets.h>
 #endif
@@ -477,7 +475,7 @@ int io_sync(IOHANDLE io)
 // TODO: Use Thread Safety Analysis when this file is converted to C++
 struct ASYNCIO
 {
-	LOCK lock;
+	std::mutex mutex;
 	IOHANDLE io;
 	SEMAPHORE sphore;
 	void *thread;
@@ -524,19 +522,18 @@ static void buffer_ptrs(ASYNCIO *aio, struct BUFFERS *buffers)
 	}
 }
 
-static void aio_handle_free_and_unlock(ASYNCIO *aio) RELEASE(aio->lock)
+static void aio_handle_free_and_unlock(ASYNCIO *aio) RELEASE(aio->mutex)
 {
 	int do_free;
 	aio->refcount--;
 
 	do_free = aio->refcount == 0;
-	lock_unlock(aio->lock);
+	aio->mutex.unlock();
 	if(do_free)
 	{
 		free(aio->buffer);
 		sphore_destroy(&aio->sphore);
-		lock_destroy(aio->lock);
-		free(aio);
+		delete aio;
 	}
 }
 
@@ -544,7 +541,7 @@ static void aio_thread(void *user)
 {
 	ASYNCIO *aio = (ASYNCIO *)user;
 
-	lock_wait(aio->lock);
+	aio->mutex.lock();
 	while(true)
 	{
 		struct BUFFERS buffers;
@@ -563,9 +560,9 @@ static void aio_thread(void *user)
 				aio_handle_free_and_unlock(aio);
 				break;
 			}
-			lock_unlock(aio->lock);
+			aio->mutex.unlock();
 			sphore_wait(&aio->sphore);
-			lock_wait(aio->lock);
+			aio->mutex.lock();
 			continue;
 		}
 
@@ -589,26 +586,25 @@ static void aio_thread(void *user)
 			}
 		}
 		aio->read_pos = (aio->read_pos + buffers.len1 + buffers.len2) % aio->buffer_size;
-		lock_unlock(aio->lock);
+		aio->mutex.unlock();
 
 		io_write(aio->io, local_buffer, local_buffer_len);
 		io_flush(aio->io);
 		result_io_error = io_error(aio->io);
 
-		lock_wait(aio->lock);
+		aio->mutex.lock();
 		aio->error = result_io_error;
 	}
 }
 
 ASYNCIO *aio_new(IOHANDLE io)
 {
-	ASYNCIO *aio = (ASYNCIO *)malloc(sizeof(*aio));
+	ASYNCIO *aio = new ASYNCIO;
 	if(!aio)
 	{
 		return 0;
 	}
 	aio->io = io;
-	aio->lock = lock_create();
 	sphore_init(&aio->sphore);
 	aio->thread = 0;
 
@@ -616,8 +612,7 @@ ASYNCIO *aio_new(IOHANDLE io)
 	if(!aio->buffer)
 	{
 		sphore_destroy(&aio->sphore);
-		lock_destroy(aio->lock);
-		free(aio);
+		delete aio;
 		return 0;
 	}
 	aio->buffer_size = ASYNC_BUFSIZE;
@@ -632,8 +627,7 @@ ASYNCIO *aio_new(IOHANDLE io)
 	{
 		free(aio->buffer);
 		sphore_destroy(&aio->sphore);
-		lock_destroy(aio->lock);
-		free(aio);
+		delete aio;
 		return 0;
 	}
 	return aio;
@@ -660,14 +654,14 @@ static unsigned int next_buffer_size(unsigned int cur_size, unsigned int need_si
 	return cur_size;
 }
 
-void aio_lock(ASYNCIO *aio) ACQUIRE(aio->lock)
+void aio_lock(ASYNCIO *aio) ACQUIRE(aio->mutex)
 {
-	lock_wait(aio->lock);
+	aio->mutex.lock();
 }
 
-void aio_unlock(ASYNCIO *aio) RELEASE(aio->lock)
+void aio_unlock(ASYNCIO *aio) RELEASE(aio->mutex)
 {
-	lock_unlock(aio->lock);
+	aio->mutex.unlock();
 	sphore_signal(&aio->sphore);
 }
 
@@ -746,13 +740,13 @@ void aio_write_newline(ASYNCIO *aio)
 
 int aio_error(ASYNCIO *aio)
 {
-	CLockScope ls(aio->lock);
+	const std::lock_guard<std::mutex> lock_guard(aio->mutex);
 	return aio->error;
 }
 
 void aio_free(ASYNCIO *aio)
 {
-	lock_wait(aio->lock);
+	aio->mutex.lock();
 	if(aio->thread)
 	{
 		thread_detach(aio->thread);
@@ -764,7 +758,7 @@ void aio_free(ASYNCIO *aio)
 void aio_close(ASYNCIO *aio)
 {
 	{
-		CLockScope ls(aio->lock);
+		const std::lock_guard<std::mutex> lock_guard(aio->mutex);
 		aio->finish = ASYNCIO_CLOSE;
 	}
 	sphore_signal(&aio->sphore);
@@ -774,7 +768,7 @@ void aio_wait(ASYNCIO *aio)
 {
 	void *thread;
 	{
-		CLockScope ls(aio->lock);
+		const std::lock_guard<std::mutex> lock_guard(aio->mutex);
 		thread = aio->thread;
 		aio->thread = 0;
 		if(aio->finish == ASYNCIO_RUNNING)
@@ -877,106 +871,6 @@ void thread_detach(void *thread)
 #endif
 }
 
-bool thread_init_and_detach(void (*threadfunc)(void *), void *u, const char *name)
-{
-	void *thread = thread_init(threadfunc, u, name);
-	if(thread)
-		thread_detach(thread);
-	return thread != nullptr;
-}
-
-#if defined(CONF_FAMILY_UNIX)
-typedef pthread_mutex_t LOCKINTERNAL;
-#elif defined(CONF_FAMILY_WINDOWS)
-typedef CRITICAL_SECTION LOCKINTERNAL;
-#else
-#error not implemented on this platform
-#endif
-
-LOCK lock_create()
-{
-	LOCKINTERNAL *lock = (LOCKINTERNAL *)malloc(sizeof(*lock));
-#if defined(CONF_FAMILY_UNIX)
-	int result;
-#endif
-
-	if(!lock)
-		return 0;
-
-#if defined(CONF_FAMILY_UNIX)
-	result = pthread_mutex_init(lock, 0x0);
-	if(result != 0)
-	{
-		dbg_msg("lock", "init failed: %d", result);
-		free(lock);
-		return 0;
-	}
-#elif defined(CONF_FAMILY_WINDOWS)
-	InitializeCriticalSection((LPCRITICAL_SECTION)lock);
-#else
-#error not implemented on this platform
-#endif
-	return (LOCK)lock;
-}
-
-void lock_destroy(LOCK lock)
-{
-#if defined(CONF_FAMILY_UNIX)
-	int result = pthread_mutex_destroy((LOCKINTERNAL *)lock);
-	if(result != 0)
-		dbg_msg("lock", "destroy failed: %d", result);
-#elif defined(CONF_FAMILY_WINDOWS)
-	DeleteCriticalSection((LPCRITICAL_SECTION)lock);
-#else
-#error not implemented on this platform
-#endif
-	free(lock);
-}
-
-int lock_trylock(LOCK lock)
-{
-#if defined(CONF_FAMILY_UNIX)
-	return pthread_mutex_trylock((LOCKINTERNAL *)lock);
-#elif defined(CONF_FAMILY_WINDOWS)
-	return !TryEnterCriticalSection((LPCRITICAL_SECTION)lock);
-#else
-#error not implemented on this platform
-#endif
-}
-
-#ifdef __clang__
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wthread-safety-analysis"
-#endif
-void lock_wait(LOCK lock)
-{
-#if defined(CONF_FAMILY_UNIX)
-	int result = pthread_mutex_lock((LOCKINTERNAL *)lock);
-	if(result != 0)
-		dbg_msg("lock", "lock failed: %d", result);
-#elif defined(CONF_FAMILY_WINDOWS)
-	EnterCriticalSection((LPCRITICAL_SECTION)lock);
-#else
-#error not implemented on this platform
-#endif
-}
-
-void lock_unlock(LOCK lock)
-{
-#if defined(CONF_FAMILY_UNIX)
-	int result = pthread_mutex_unlock((LOCKINTERNAL *)lock);
-	if(result != 0)
-		dbg_msg("lock", "unlock failed: %d", result);
-#elif defined(CONF_FAMILY_WINDOWS)
-	LeaveCriticalSection((LPCRITICAL_SECTION)lock);
-#else
-#error not implemented on this platform
-#endif
-}
-#ifdef __clang__
-#pragma clang diagnostic pop
-#endif
-
 #if defined(CONF_FAMILY_WINDOWS)
 void sphore_init(SEMAPHORE *sem)
 {
diff --git a/src/base/system.h b/src/base/system.h
index 0347df22b57..03a234da7a9 100644
--- a/src/base/system.h
+++ b/src/base/system.h
@@ -594,19 +594,6 @@ void thread_yield();
  */
 void thread_detach(void *thread);
 
-/**
- * Creates a new thread and if it succeeded detaches it.
- *
- * @ingroup Threads
- *
- * @param threadfunc Entry point for the new thread.
- * @param user Pointer to pass to the thread.
- * @param name Name describing the use of the thread
- *
- * @return true on success, false on failure.
- */
-bool thread_init_and_detach(void (*threadfunc)(void *), void *user, const char *name);
-
 // Enable thread safety attributes only with clang.
 // The attributes can be safely erased when compiling with other compilers.
 #if defined(__clang__) && (!defined(SWIG))
@@ -675,6 +662,9 @@ bool thread_init_and_detach(void (*threadfunc)(void *), void *user, const char *
 #define NO_THREAD_SAFETY_ANALYSIS \
 	THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)
 
+// Enable thread-safety annotations for std::mutex
+#define _LIBCPP_HAS_THREAD_SAFETY_ANNOTATIONS
+
 /**
  * @defgroup Locks
  *
@@ -683,30 +673,6 @@ bool thread_init_and_detach(void (*threadfunc)(void *), void *user, const char *
  * @see Threads
  */
 
-typedef CAPABILITY("mutex") void *LOCK;
-
-/**
- * @ingroup Locks
- */
-LOCK lock_create();
-/**
- * @ingroup Locks
- */
-void lock_destroy(LOCK lock);
-
-/**
- * @ingroup Locks
- */
-int lock_trylock(LOCK lock) TRY_ACQUIRE(1, lock);
-/**
- * @ingroup Locks
- */
-void lock_wait(LOCK lock) ACQUIRE(lock);
-/**
- * @ingroup Locks
- */
-void lock_unlock(LOCK lock) RELEASE(lock);
-
 /* Group: Semaphores */
 #if defined(CONF_FAMILY_WINDOWS)
 typedef void *SEMAPHORE;
diff --git a/src/engine/client/serverbrowser_http.cpp b/src/engine/client/serverbrowser_http.cpp
index 978ac9e16c7..e596cb6889b 100644
--- a/src/engine/client/serverbrowser_http.cpp
+++ b/src/engine/client/serverbrowser_http.cpp
@@ -1,5 +1,7 @@
 #include "serverbrowser_http.h"
 
+#include <base/system.h>
+
 #include <engine/console.h>
 #include <engine/engine.h>
 #include <engine/external/json-parser/json.h>
@@ -10,14 +12,10 @@
 #include <engine/shared/serverinfo.h>
 #include <engine/storage.h>
 
-#include <base/lock_scope.h>
-#include <base/system.h>
-
+#include <chrono>
 #include <memory>
 #include <vector>
 
-#include <chrono>
-
 using namespace std::chrono_literals;
 
 class CChooseMaster
@@ -51,17 +49,16 @@ class CChooseMaster
 	};
 	class CJob : public IJob
 	{
-		LOCK m_Lock;
+		std::mutex m_Mutex;
 		std::shared_ptr<CData> m_pData;
-		std::unique_ptr<CHttpRequest> m_pHead PT_GUARDED_BY(m_Lock);
-		std::unique_ptr<CHttpRequest> m_pGet PT_GUARDED_BY(m_Lock);
-		void Run() override REQUIRES(!m_Lock);
+		std::unique_ptr<CHttpRequest> m_pHead PT_GUARDED_BY(m_Mutex);
+		std::unique_ptr<CHttpRequest> m_pGet PT_GUARDED_BY(m_Mutex);
+		void Run() override REQUIRES(!m_Mutex);
 
 	public:
 		CJob(std::shared_ptr<CData> pData) :
-			m_pData(std::move(pData)) { m_Lock = lock_create(); }
-		~CJob() override { lock_destroy(m_Lock); }
-		void Abort() REQUIRES(!m_Lock);
+			m_pData(std::move(pData)) {}
+		void Abort() REQUIRES(!m_Mutex);
 	};
 
 	IEngine *m_pEngine;
@@ -134,7 +131,7 @@ void CChooseMaster::Refresh()
 
 void CChooseMaster::CJob::Abort()
 {
-	CLockScope ls(m_Lock);
+	const std::lock_guard<std::mutex> LockGuard(m_Mutex);
 	if(m_pHead != nullptr)
 	{
 		m_pHead->Abort();
@@ -176,7 +173,7 @@ void CChooseMaster::CJob::Run()
 		pHead->Timeout(Timeout);
 		pHead->LogProgress(HTTPLOG::FAILURE);
 		{
-			CLockScope ls(m_Lock);
+			const std::lock_guard<std::mutex> LockGuard(m_Mutex);
 			m_pHead = std::unique_ptr<CHttpRequest>(pHead);
 		}
 		IEngine::RunJobBlocking(pHead);
@@ -194,7 +191,7 @@ void CChooseMaster::CJob::Run()
 		pGet->Timeout(Timeout);
 		pGet->LogProgress(HTTPLOG::FAILURE);
 		{
-			CLockScope ls(m_Lock);
+			const std::lock_guard<std::mutex> LockGuard(m_Mutex);
 			m_pGet = std::unique_ptr<CHttpRequest>(pGet);
 		}
 		IEngine::RunJobBlocking(pGet);
diff --git a/src/engine/client/updater.cpp b/src/engine/client/updater.cpp
index 16a03048b35..386536bb799 100644
--- a/src/engine/client/updater.cpp
+++ b/src/engine/client/updater.cpp
@@ -1,6 +1,7 @@
 #include "updater.h"
-#include <base/lock_scope.h>
+
 #include <base/system.h>
+
 #include <engine/client.h>
 #include <engine/engine.h>
 #include <engine/external/json-parser/json.h>
@@ -55,7 +56,7 @@ CUpdaterFetchTask::CUpdaterFetchTask(CUpdater *pUpdater, const char *pFile, cons
 
 void CUpdaterFetchTask::OnProgress()
 {
-	CLockScope ls(m_pUpdater->m_Lock);
+	const std::lock_guard<std::mutex> LockGuard(m_pUpdater->m_Mutex);
 	str_copy(m_pUpdater->m_aStatus, Dest());
 	m_pUpdater->m_Percent = Progress();
 }
@@ -94,7 +95,6 @@ CUpdater::CUpdater()
 	m_pEngine = NULL;
 	m_State = CLEAN;
 	m_Percent = 0;
-	m_Lock = lock_create();
 
 	IStorage::FormatTmpPath(m_aClientExecTmp, sizeof(m_aClientExecTmp), CLIENT_EXEC);
 	IStorage::FormatTmpPath(m_aServerExecTmp, sizeof(m_aServerExecTmp), SERVER_EXEC);
@@ -107,32 +107,27 @@ void CUpdater::Init()
 	m_pEngine = Kernel()->RequestInterface<IEngine>();
 }
 
-CUpdater::~CUpdater()
-{
-	lock_destroy(m_Lock);
-}
-
 void CUpdater::SetCurrentState(int NewState)
 {
-	CLockScope ls(m_Lock);
+	const std::lock_guard<std::mutex> LockGuard(m_Mutex);
 	m_State = NewState;
 }
 
 int CUpdater::GetCurrentState()
 {
-	CLockScope ls(m_Lock);
+	const std::lock_guard<std::mutex> LockGuard(m_Mutex);
 	return m_State;
 }
 
 void CUpdater::GetCurrentFile(char *pBuf, int BufSize)
 {
-	CLockScope ls(m_Lock);
+	const std::lock_guard<std::mutex> LockGuard(m_Mutex);
 	str_copy(pBuf, m_aStatus, BufSize);
 }
 
 int CUpdater::GetCurrentPercent()
 {
-	CLockScope ls(m_Lock);
+	const std::lock_guard<std::mutex> LockGuard(m_Mutex);
 	return m_Percent;
 }
 
diff --git a/src/engine/client/updater.h b/src/engine/client/updater.h
index 7571d58675e..69b29c65e85 100644
--- a/src/engine/client/updater.h
+++ b/src/engine/client/updater.h
@@ -2,8 +2,10 @@
 #define ENGINE_CLIENT_UPDATER_H
 
 #include <engine/updater.h>
+
 #include <map>
 #include <string>
+#include <mutex>
 
 #define CLIENT_EXEC "DDNet"
 #define SERVER_EXEC "DDNet-Server"
@@ -39,11 +41,11 @@ class CUpdater : public IUpdater
 	class IStorage *m_pStorage;
 	class IEngine *m_pEngine;
 
-	LOCK m_Lock;
+	std::mutex m_Mutex;
 
 	int m_State;
-	char m_aStatus[256] GUARDED_BY(m_Lock);
-	int m_Percent GUARDED_BY(m_Lock);
+	char m_aStatus[256] GUARDED_BY(m_Mutex);
+	int m_Percent GUARDED_BY(m_Mutex);
 	char m_aLastFile[256];
 	char m_aClientExecTmp[64];
 	char m_aServerExecTmp[64];
@@ -64,15 +66,14 @@ class CUpdater : public IUpdater
 	bool ReplaceClient();
 	bool ReplaceServer();
 
-	void SetCurrentState(int NewState) REQUIRES(!m_Lock);
+	void SetCurrentState(int NewState) REQUIRES(!m_Mutex);
 
 public:
 	CUpdater();
-	~CUpdater();
 
-	int GetCurrentState() override REQUIRES(!m_Lock);
-	void GetCurrentFile(char *pBuf, int BufSize) override REQUIRES(!m_Lock);
-	int GetCurrentPercent() override REQUIRES(!m_Lock);
+	int GetCurrentState() override REQUIRES(!m_Mutex);
+	void GetCurrentFile(char *pBuf, int BufSize) override REQUIRES(!m_Mutex);
+	int GetCurrentPercent() override REQUIRES(!m_Mutex);
 
 	void InitiateUpdate() override;
 	void Init();
diff --git a/src/engine/client/video.cpp b/src/engine/client/video.cpp
index 8fe839a8f59..879f004c78a 100644
--- a/src/engine/client/video.cpp
+++ b/src/engine/client/video.cpp
@@ -1,11 +1,11 @@
 #if defined(CONF_VIDEORECORDER)
 
-#include <engine/shared/config.h>
-#include <engine/storage.h>
+#include "video.h"
 
-#include <base/lock_scope.h>
 #include <engine/client/graphics_threaded.h>
+#include <engine/shared/config.h>
 #include <engine/sound.h>
+#include <engine/storage.h>
 
 extern "C" {
 #include <libavutil/avutil.h>
@@ -14,12 +14,9 @@ extern "C" {
 #include <libswscale/swscale.h>
 };
 
+#include <chrono>
 #include <memory>
 #include <mutex>
-
-#include "video.h"
-
-#include <chrono>
 #include <thread>
 
 using namespace std::chrono_literals;
@@ -35,7 +32,7 @@ using namespace std::chrono_literals;
 #endif
 
 const size_t FORMAT_GL_NCHANNELS = 4;
-LOCK g_WriteLock = 0;
+std::mutex g_WriteMutex;
 
 CVideo::CVideo(CGraphics_Threaded *pGraphics, ISound *pSound, IStorage *pStorage, int Width, int Height, const char *pName) :
 	m_pGraphics(pGraphics),
@@ -66,13 +63,11 @@ CVideo::CVideo(CGraphics_Threaded *pGraphics, ISound *pSound, IStorage *pStorage
 
 	ms_TickTime = time_freq() / m_FPS;
 	ms_pCurrentVideo = this;
-	g_WriteLock = lock_create();
 }
 
 CVideo::~CVideo()
 {
 	ms_pCurrentVideo = 0;
-	lock_destroy(g_WriteLock);
 }
 
 void CVideo::Start()
@@ -165,7 +160,7 @@ void CVideo::Start()
 	for(size_t i = 0; i < m_VideoThreads; ++i)
 	{
 		std::unique_lock<std::mutex> Lock(m_vVideoThreads[i]->m_Mutex);
-		m_vVideoThreads[i]->m_Thread = std::thread([this, i]() REQUIRES(!g_WriteLock) { RunVideoThread(i == 0 ? (m_VideoThreads - 1) : (i - 1), i); });
+		m_vVideoThreads[i]->m_Thread = std::thread([this, i]() REQUIRES(!g_WriteMutex) { RunVideoThread(i == 0 ? (m_VideoThreads - 1) : (i - 1), i); });
 		m_vVideoThreads[i]->m_Cond.wait(Lock, [this, i]() -> bool { return m_vVideoThreads[i]->m_Started; });
 	}
 
@@ -177,7 +172,7 @@ void CVideo::Start()
 	for(size_t i = 0; i < m_AudioThreads; ++i)
 	{
 		std::unique_lock<std::mutex> Lock(m_vAudioThreads[i]->m_Mutex);
-		m_vAudioThreads[i]->m_Thread = std::thread([this, i]() REQUIRES(!g_WriteLock) { RunAudioThread(i == 0 ? (m_AudioThreads - 1) : (i - 1), i); });
+		m_vAudioThreads[i]->m_Thread = std::thread([this, i]() REQUIRES(!g_WriteMutex) { RunAudioThread(i == 0 ? (m_AudioThreads - 1) : (i - 1), i); });
 		m_vAudioThreads[i]->m_Cond.wait(Lock, [this, i]() -> bool { return m_vAudioThreads[i]->m_Started; });
 	}
 
@@ -476,7 +471,7 @@ void CVideo::RunAudioThread(size_t ParentThreadIndex, size_t ThreadIndex)
 				std::unique_lock<std::mutex> LockAudio(pThreadData->m_AudioFillMutex);
 
 				{
-					CLockScope ls(g_WriteLock);
+					const std::lock_guard<std::mutex> LockGuard(g_WriteMutex);
 					m_AudioStream.m_vpFrames[ThreadIndex]->pts = av_rescale_q(pThreadData->m_SampleCountStart, AVRational{1, m_AudioStream.pEnc->sample_rate}, m_AudioStream.pEnc->time_base);
 					WriteFrame(&m_AudioStream, ThreadIndex);
 				}
@@ -557,7 +552,7 @@ void CVideo::RunVideoThread(size_t ParentThreadIndex, size_t ThreadIndex)
 			{
 				std::unique_lock<std::mutex> LockVideo(pThreadData->m_VideoFillMutex);
 				{
-					CLockScope ls(g_WriteLock);
+					const std::lock_guard<std::mutex> LockGuard(g_WriteMutex);
 					m_VideoStream.m_vpFrames[ThreadIndex]->pts = (int64_t)m_VideoStream.pEnc->FRAME_NUM;
 					WriteFrame(&m_VideoStream, ThreadIndex);
 				}
diff --git a/src/engine/client/video.h b/src/engine/client/video.h
index 38a4ec02c69..63a1e7114d9 100644
--- a/src/engine/client/video.h
+++ b/src/engine/client/video.h
@@ -21,7 +21,7 @@ class CGraphics_Threaded;
 class ISound;
 class IStorage;
 
-extern LOCK g_WriteLock;
+extern std::mutex g_WriteMutex;
 
 // a wrapper around a single output AVStream
 struct OutputStream
@@ -47,7 +47,7 @@ class CVideo : public IVideo
 	CVideo(CGraphics_Threaded *pGraphics, ISound *pSound, IStorage *pStorage, int Width, int Height, const char *pName);
 	~CVideo();
 
-	void Start() override REQUIRES(!g_WriteLock);
+	void Start() override REQUIRES(!g_WriteMutex);
 	void Stop() override;
 	void Pause(bool Pause) override;
 	bool IsRecording() override { return m_Recording; }
@@ -63,11 +63,11 @@ class CVideo : public IVideo
 	static void Init() { av_log_set_level(AV_LOG_DEBUG); }
 
 private:
-	void RunVideoThread(size_t ParentThreadIndex, size_t ThreadIndex) REQUIRES(!g_WriteLock);
-	void FillVideoFrame(size_t ThreadIndex) REQUIRES(!g_WriteLock);
+	void RunVideoThread(size_t ParentThreadIndex, size_t ThreadIndex) REQUIRES(!g_WriteMutex);
+	void FillVideoFrame(size_t ThreadIndex) REQUIRES(!g_WriteMutex);
 	void ReadRGBFromGL(size_t ThreadIndex);
 
-	void RunAudioThread(size_t ParentThreadIndex, size_t ThreadIndex) REQUIRES(!g_WriteLock);
+	void RunAudioThread(size_t ParentThreadIndex, size_t ThreadIndex) REQUIRES(!g_WriteMutex);
 	void FillAudioFrame(size_t ThreadIndex);
 
 	bool OpenVideo();
@@ -75,7 +75,7 @@ class CVideo : public IVideo
 	AVFrame *AllocPicture(enum AVPixelFormat PixFmt, int Width, int Height);
 	AVFrame *AllocAudioFrame(enum AVSampleFormat SampleFmt, uint64_t ChannelLayout, int SampleRate, int NbSamples);
 
-	void WriteFrame(OutputStream *pStream, size_t ThreadIndex) REQUIRES(g_WriteLock);
+	void WriteFrame(OutputStream *pStream, size_t ThreadIndex) REQUIRES(g_WriteMutex);
 	void FinishFrames(OutputStream *pStream);
 	void CloseStream(OutputStream *pStream);
 
diff --git a/src/engine/server/register.cpp b/src/engine/server/register.cpp
index b262b77aec9..94c7395019c 100644
--- a/src/engine/server/register.cpp
+++ b/src/engine/server/register.cpp
@@ -1,7 +1,7 @@
 #include "register.h"
 
-#include <base/lock_scope.h>
 #include <base/log.h>
+
 #include <engine/console.h>
 #include <engine/engine.h>
 #include <engine/shared/config.h>
@@ -12,6 +12,8 @@
 #include <engine/shared/packer.h>
 #include <engine/shared/uuid_manager.h>
 
+#include <mutex>
+
 class CRegister : public IRegister
 {
 	enum
@@ -40,14 +42,9 @@ class CRegister : public IRegister
 	class CGlobal
 	{
 	public:
-		~CGlobal()
-		{
-			lock_destroy(m_Lock);
-		}
-
-		LOCK m_Lock = lock_create();
-		int m_InfoSerial GUARDED_BY(m_Lock) = -1;
-		int m_LatestSuccessfulInfoSerial GUARDED_BY(m_Lock) = -1;
+		std::mutex m_Mutex;
+		int m_InfoSerial GUARDED_BY(m_Mutex) = -1;
+		int m_LatestSuccessfulInfoSerial GUARDED_BY(m_Mutex) = -1;
 	};
 
 	class CProtocol
@@ -59,16 +56,12 @@ class CRegister : public IRegister
 				m_pGlobal(std::move(pGlobal))
 			{
 			}
-			~CShared()
-			{
-				lock_destroy(m_Lock);
-			}
 
 			std::shared_ptr<CGlobal> m_pGlobal;
-			LOCK m_Lock = lock_create();
-			int m_NumTotalRequests GUARDED_BY(m_Lock) = 0;
-			int m_LatestResponseStatus GUARDED_BY(m_Lock) = STATUS_NONE;
-			int m_LatestResponseIndex GUARDED_BY(m_Lock) = -1;
+			std::mutex m_Mutex;
+			int m_NumTotalRequests GUARDED_BY(m_Mutex) = 0;
+			int m_LatestResponseStatus GUARDED_BY(m_Mutex) = STATUS_NONE;
+			int m_LatestResponseIndex GUARDED_BY(m_Mutex) = -1;
 		};
 
 		class CJob : public IJob
@@ -274,7 +267,7 @@ void CRegister::CProtocol::SendRegister()
 	bool SendInfo;
 
 	{
-		CLockScope ls(m_pShared->m_pGlobal->m_Lock);
+		const std::lock_guard<std::mutex> LockGuard(m_pShared->m_pGlobal->m_Mutex);
 		InfoSerial = m_pShared->m_pGlobal->m_InfoSerial;
 		SendInfo = InfoSerial > m_pShared->m_pGlobal->m_LatestSuccessfulInfoSerial;
 	}
@@ -309,7 +302,7 @@ void CRegister::CProtocol::SendRegister()
 
 	int RequestIndex;
 	{
-		CLockScope ls(m_pShared->m_Lock);
+		const std::lock_guard<std::mutex> LockGuard(m_pShared->m_Mutex);
 		if(m_pShared->m_LatestResponseStatus != STATUS_OK)
 		{
 			log_info(ProtocolToSystem(m_Protocol), "registering...");
@@ -326,13 +319,12 @@ void CRegister::CProtocol::SendRegister()
 
 void CRegister::CProtocol::SendDeleteIfRegistered(bool Shutdown)
 {
-	lock_wait(m_pShared->m_Lock);
-	bool ShouldSendDelete = m_pShared->m_LatestResponseStatus == STATUS_OK;
-	m_pShared->m_LatestResponseStatus = STATUS_NONE;
-	lock_unlock(m_pShared->m_Lock);
-	if(!ShouldSendDelete)
 	{
-		return;
+		const std::lock_guard<std::mutex> LockGuard(m_pShared->m_Mutex);
+		const bool ShouldSendDelete = m_pShared->m_LatestResponseStatus == STATUS_OK;
+		m_pShared->m_LatestResponseStatus = STATUS_NONE;
+		if(!ShouldSendDelete)
+			return;
 	}
 
 	char aAddress[64];
@@ -369,7 +361,7 @@ CRegister::CProtocol::CProtocol(CRegister *pParent, int Protocol) :
 
 void CRegister::CProtocol::CheckChallengeStatus()
 {
-	CLockScope ls(m_pShared->m_Lock);
+	const std::lock_guard<std::mutex> LockGuard(m_pShared->m_Mutex);
 	// No requests in flight?
 	if(m_pShared->m_LatestResponseIndex == m_pShared->m_NumTotalRequests - 1)
 	{
@@ -444,7 +436,7 @@ void CRegister::CProtocol::CJob::Run()
 		return;
 	}
 	{
-		CLockScope ls(m_pShared->m_Lock);
+		const std::lock_guard<std::mutex> LockGuard(m_pShared->m_Mutex);
 		if(Status != STATUS_OK || Status != m_pShared->m_LatestResponseStatus)
 		{
 			log_debug(ProtocolToSystem(m_Protocol), "status: %s", (const char *)StatusString);
@@ -463,7 +455,7 @@ void CRegister::CProtocol::CJob::Run()
 	}
 	if(Status == STATUS_OK)
 	{
-		CLockScope ls(m_pShared->m_pGlobal->m_Lock);
+		const std::lock_guard<std::mutex> LockGuard(m_pShared->m_pGlobal->m_Mutex);
 		if(m_InfoSerial > m_pShared->m_pGlobal->m_LatestSuccessfulInfoSerial)
 		{
 			m_pShared->m_pGlobal->m_LatestSuccessfulInfoSerial = m_InfoSerial;
@@ -471,7 +463,7 @@ void CRegister::CProtocol::CJob::Run()
 	}
 	else if(Status == STATUS_NEEDINFO)
 	{
-		CLockScope ls(m_pShared->m_pGlobal->m_Lock);
+		const std::lock_guard<std::mutex> LockGuard(m_pShared->m_pGlobal->m_Mutex);
 		if(m_InfoSerial == m_pShared->m_pGlobal->m_LatestSuccessfulInfoSerial)
 		{
 			// Tell other requests that they need to send the info again.
@@ -692,7 +684,7 @@ void CRegister::OnNewInfo(const char *pInfo)
 	m_GotServerInfo = true;
 	str_copy(m_aServerInfo, pInfo);
 	{
-		CLockScope ls(m_pGlobal->m_Lock);
+		const std::lock_guard<std::mutex> LockGuard(m_pGlobal->m_Mutex);
 		m_pGlobal->m_InfoSerial += 1;
 	}
 
diff --git a/src/engine/shared/http.cpp b/src/engine/shared/http.cpp
index d024ba5e95f..f1421230dcc 100644
--- a/src/engine/shared/http.cpp
+++ b/src/engine/shared/http.cpp
@@ -12,15 +12,17 @@
 #include <csignal>
 #endif
 
+#include <mutex>
+
 #define WIN32_LEAN_AND_MEAN
 #include <curl/curl.h>
 
 // TODO: Non-global pls?
 static CURLSH *gs_pShare;
-static LOCK gs_aLocks[CURL_LOCK_DATA_LAST + 1];
+static std::mutex gs_aMutexes[CURL_LOCK_DATA_LAST + 1];
 static bool gs_Initialized = false;
 
-static int GetLockIndex(int Data)
+static int GetMutexIndex(int Data)
 {
 	if(!(0 <= Data && Data < CURL_LOCK_DATA_LAST))
 	{
@@ -29,19 +31,19 @@ static int GetLockIndex(int Data)
 	return Data;
 }
 
-static void CurlLock(CURL *pHandle, curl_lock_data Data, curl_lock_access Access, void *pUser) ACQUIRE(gs_aLocks[GetLockIndex(Data)])
+static void CurlLock(CURL *pHandle, curl_lock_data Data, curl_lock_access Access, void *pUser) ACQUIRE(gs_aMutexes[GetMutexIndex(Data)])
 {
 	(void)pHandle;
 	(void)Access;
 	(void)pUser;
-	lock_wait(gs_aLocks[GetLockIndex(Data)]);
+	gs_aMutexes[GetMutexIndex(Data)].lock();
 }
 
-static void CurlUnlock(CURL *pHandle, curl_lock_data Data, void *pUser) RELEASE(gs_aLocks[GetLockIndex(Data)])
+static void CurlUnlock(CURL *pHandle, curl_lock_data Data, void *pUser) RELEASE(gs_aMutexes[GetMutexIndex(Data)])
 {
 	(void)pHandle;
 	(void)pUser;
-	lock_unlock(gs_aLocks[GetLockIndex(Data)]);
+	gs_aMutexes[GetMutexIndex(Data)].unlock();
 }
 
 int CurlDebug(CURL *pHandle, curl_infotype Type, char *pData, size_t DataSize, void *pUser)
@@ -88,10 +90,6 @@ bool HttpInit(IStorage *pStorage)
 		dbg_msg("http", "libcurl version %s (compiled = " LIBCURL_VERSION ")", pVersion->version);
 	}
 
-	for(auto &Lock : gs_aLocks)
-	{
-		Lock = lock_create();
-	}
 	curl_share_setopt(gs_pShare, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
 	curl_share_setopt(gs_pShare, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
 	curl_share_setopt(gs_pShare, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
diff --git a/src/engine/shared/jobs.cpp b/src/engine/shared/jobs.cpp
index 36ee17d8d11..a25a0f4f0f5 100644
--- a/src/engine/shared/jobs.cpp
+++ b/src/engine/shared/jobs.cpp
@@ -1,8 +1,7 @@
 /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
 /* If you are missing that file, acquire a complete release at teeworlds.com.                */
-#include "jobs.h"
 
-#include <base/lock_scope.h>
+#include "jobs.h"
 
 IJob::IJob() :
 	m_Status(STATE_PENDING)
@@ -20,7 +19,6 @@ CJobPool::CJobPool()
 {
 	// empty the pool
 	m_Shutdown = false;
-	m_Lock = lock_create();
 	sphore_init(&m_Semaphore);
 	m_pFirstJob = 0;
 	m_pLastJob = 0;
@@ -45,7 +43,7 @@ void CJobPool::WorkerThread(void *pUser)
 		// fetch job from queue
 		sphore_wait(&pPool->m_Semaphore);
 		{
-			CLockScope ls(pPool->m_Lock);
+			const std::lock_guard<std::mutex> LockGuard(pPool->m_Mutex);
 			if(pPool->m_pFirstJob)
 			{
 				pJob = pPool->m_pFirstJob;
@@ -85,14 +83,13 @@ void CJobPool::Destroy()
 	for(void *pThread : m_vpThreads)
 		thread_wait(pThread);
 	m_vpThreads.clear();
-	lock_destroy(m_Lock);
 	sphore_destroy(&m_Semaphore);
 }
 
 void CJobPool::Add(std::shared_ptr<IJob> pJob)
 {
 	{
-		CLockScope ls(m_Lock);
+		const std::lock_guard<std::mutex> LockGuard(m_Mutex);
 		// add job to queue
 		if(m_pLastJob)
 			m_pLastJob->m_pNext = pJob;
diff --git a/src/engine/shared/jobs.h b/src/engine/shared/jobs.h
index 2a24efbdcc0..1c500b5ecdf 100644
--- a/src/engine/shared/jobs.h
+++ b/src/engine/shared/jobs.h
@@ -7,6 +7,7 @@
 
 #include <atomic>
 #include <memory>
+#include <mutex>
 #include <vector>
 
 class CJobPool;
@@ -41,10 +42,10 @@ class CJobPool
 	std::vector<void *> m_vpThreads;
 	std::atomic<bool> m_Shutdown;
 
-	LOCK m_Lock;
+	std::mutex m_Mutex;
 	SEMAPHORE m_Semaphore;
-	std::shared_ptr<IJob> m_pFirstJob GUARDED_BY(m_Lock);
-	std::shared_ptr<IJob> m_pLastJob GUARDED_BY(m_Lock);
+	std::shared_ptr<IJob> m_pFirstJob GUARDED_BY(m_Mutex);
+	std::shared_ptr<IJob> m_pLastJob GUARDED_BY(m_Mutex);
 
 	static void WorkerThread(void *pUser) NO_THREAD_SAFETY_ANALYSIS;
 
@@ -54,7 +55,7 @@ class CJobPool
 
 	void Init(int NumThreads);
 	void Destroy();
-	void Add(std::shared_ptr<IJob> pJob) REQUIRES(!m_Lock);
+	void Add(std::shared_ptr<IJob> pJob) REQUIRES(!m_Mutex);
 	static void RunBlocking(IJob *pJob);
 };
 #endif
diff --git a/src/test/thread.cpp b/src/test/thread.cpp
index e0e242b4394..20109e71207 100644
--- a/src/test/thread.cpp
+++ b/src/test/thread.cpp
@@ -79,20 +79,3 @@ TEST(Thread, SemaphoreMultiThreaded)
 	thread_wait(pThread);
 	sphore_destroy(&Semaphore);
 }
-
-static void LockThread(void *pUser)
-{
-	LOCK *pLock = (LOCK *)pUser;
-	lock_wait(*pLock);
-	lock_unlock(*pLock);
-}
-
-TEST(Thread, Lock)
-{
-	LOCK Lock = lock_create();
-	lock_wait(Lock);
-	void *pThread = thread_init(LockThread, &Lock, "lock");
-	lock_unlock(Lock);
-	thread_wait(pThread);
-	lock_destroy(Lock);
-}