Port kleisauke's https://github.com/emscripten-core/emscripten/commit/d5d5f69ad066aee2e22a601921411b94206d035d
diff --git a/system/lib/libc/emscripten_pthread.c b/system/lib/libc/emscripten_pthread.c
index 9221622..1cf2641 100644
--- a/system/lib/libc/emscripten_pthread.c
+++ b/system/lib/libc/emscripten_pthread.c
@@ -1,6 +1,7 @@
 #include <pthread.h>
 #include "libc.h"
 #include "pthread_impl.h"
+#include "stdio_impl.h"
 
 #if !__EMSCRIPTEN_PTHREADS__
 static struct pthread __main_pthread;
@@ -24,6 +25,17 @@
   PThread.initRuntime();
 })
 
+static void init_file_lock(FILE *f)
+{
+  if (f && f->lock<0) f->lock = 0;
+}
+
+// std{in,out,err}.c will override this if linked
+static FILE *volatile dummy_file = 0;
+weak_alias(dummy_file, __stdin_used);
+weak_alias(dummy_file, __stdout_used);
+weak_alias(dummy_file, __stderr_used);
+
 // This must run before any userland ctors
 // Note that ASan constructor priority is 50, and we must be higher.
 EMSCRIPTEN_KEEPALIVE
@@ -31,5 +43,10 @@
 void __emscripten_pthread_data_constructor(void) {
   initPthreadsJS();
   pthread_self()->locale = &libc.global_locale;
+  // Ensure fprintf is thread-safe, see musl commit dba68bf98fc708cea4c478278c889fc7ad802b00
+  init_file_lock(__stdin_used);
+  init_file_lock(__stdout_used);
+  init_file_lock(__stderr_used);
+  libc.threaded = 1;
 }
 #endif
diff --git a/tests/core/test_printf_thread.c b/tests/core/test_printf_thread.c
new file mode 100644
index 0000000..4b9e147
--- /dev/null
+++ b/tests/core/test_printf_thread.c
@@ -0,0 +1,45 @@
+#include <assert.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "../../system/lib/libc/musl/include/features.h"
+#include "../../system/lib/libc/musl/src/internal/stdio_impl.h"
+#include "../../system/lib/libc/musl/src/internal/libc.h"
+
+pthread_t thread[2];
+
+char *char_repeat(int n, char c) {
+  char *dest = malloc(n + 1);
+  memset(dest, c, n);
+  dest[n] = '\0';
+  return dest;
+}
+
+void *thread_main(void *arg) {
+  char *msg = char_repeat(100, 'a');
+  for (int i = 0; i < 10; ++i)
+  printf("%s\n", msg);
+  free(msg);
+  return 0;
+}
+
+int main() {
+  printf("stdin lock: %d\n", stderr->lock);
+  printf("stdout lock: %d\n", stdin->lock);
+  printf("stderr lock: %d\n", stderr->lock);
+  printf("threaded libc: %d\n", libc.threaded);
+  int rc = pthread_create(&thread[0], NULL, thread_main, NULL);
+  assert(rc == 0);
+  rc = pthread_create(&thread[1], NULL, thread_main, NULL);
+  assert(rc == 0);
+  void *thread_rtn = 0;
+  rc = pthread_join(thread[0], &thread_rtn);
+  assert(rc == 0);
+  assert(thread_rtn == 0);
+  rc = pthread_join(thread[1], &thread_rtn);
+  assert(rc == 0);
+  assert(thread_rtn == 0);
+
+  return 0;
+}
diff --git a/tests/core/test_printf_thread.out b/tests/core/test_printf_thread.out
new file mode 100644
index 0000000..10d6ba5
--- /dev/null
+++ b/tests/core/test_printf_thread.out
@@ -0,0 +1,24 @@
+stdin lock: 0
+stdout lock: 0
+stderr lock: 0
+threaded libc: 1
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
diff --git a/tests/test_core.py b/tests/test_core.py
index a9009b2..c778877 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -8320,6 +8320,12 @@
     self.set_setting('USE_PTHREADS')
     self.do_run_in_out_file_test('core', 'pthread', 'emscripten_futexes.c')
 
+  @node_pthreads
+  def test_printf_thread(self):
+    self.set_setting('PTHREAD_POOL_SIZE', '2')
+    self.set_setting('EXIT_RUNTIME')
+    self.do_run_in_out_file_test('core', 'test_printf_thread.c')
+
   @needs_dylink
   @node_pthreads
   def test_pthread_dylink_basics(self):
diff --git a/tools/system_libs.py b/tools/system_libs.py
index 18e993e..27c2e0a 100644
--- a/tools/system_libs.py
+++ b/tools/system_libs.py
@@ -1223,7 +1223,7 @@
   force_object_files = True
 
 
-class libc_rt_wasm(OptimizedAggressivelyForSizeLibrary, AsanInstrumentedLibrary, CompilerRTLibrary, MuslInternalLibrary):
+class libc_rt_wasm(OptimizedAggressivelyForSizeLibrary, AsanInstrumentedLibrary, CompilerRTLibrary, MuslInternalLibrary, MTLibrary):
   name = 'libc_rt_wasm'
 
   def get_files(self):