diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 60894797..989cb42a 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -10,7 +10,7 @@ jobs:
     strategy:
       matrix:
         os: ["ubuntu-latest", "windows-latest", "macos-latest"]
-        py: ["3.14-dev", "3.13", "3.12", "3.11", "3.10", "3.9"]
+        py: ["3.14-dev", "3.14t-dev", "3.13", "3.13t", "3.12", "3.11", "3.10", "3.9", "3.8"]
 
     runs-on: ${{ matrix.os }}
     name: Run test with Python ${{ matrix.py }} on ${{ matrix.os }}
diff --git a/.github/workflows/wheel.yml b/.github/workflows/wheel.yml
index 12600363..2b03660d 100644
--- a/.github/workflows/wheel.yml
+++ b/.github/workflows/wheel.yml
@@ -33,7 +33,8 @@ jobs:
         env:
           CIBW_TEST_REQUIRES: "pytest"
           CIBW_TEST_COMMAND: "pytest {package}/test"
-          CIBW_SKIP: "pp* cp38-*"
+          CIBW_SKIP: "pp* cp38-macosx_*"
+          CIBW_ENABLE: cpython-freethreading
 
       - name: Build sdist
         if: runner.os == 'Linux' && runner.arch == 'X64'
diff --git a/msgpack/_cmsgpack.pyx b/msgpack/_cmsgpack.pyx
index 1faaac3a..41340ee0 100644
--- a/msgpack/_cmsgpack.pyx
+++ b/msgpack/_cmsgpack.pyx
@@ -1,5 +1,5 @@
 # coding: utf-8
-#cython: embedsignature=True, c_string_encoding=ascii, language_level=3
+#cython: embedsignature=True, c_string_encoding=ascii, language_level=3, freethreading_compatible=True
 from cpython.datetime cimport import_datetime, datetime_new
 import_datetime()
 
diff --git a/msgpack/_packer.pyx b/msgpack/_packer.pyx
index 402b6946..94d6600b 100644
--- a/msgpack/_packer.pyx
+++ b/msgpack/_packer.pyx
@@ -1,5 +1,5 @@
 # coding: utf-8
-
+# cython: freethreading_compatible = True 
 from cpython cimport *
 from cpython.bytearray cimport PyByteArray_Check, PyByteArray_CheckExact
 from cpython.datetime cimport (
diff --git a/msgpack/_unpacker.pyx b/msgpack/_unpacker.pyx
index 34ff3304..717666d6 100644
--- a/msgpack/_unpacker.pyx
+++ b/msgpack/_unpacker.pyx
@@ -1,5 +1,5 @@
 # coding: utf-8
-
+# cython: freethreading_compatible = True 
 from cpython cimport *
 cdef extern from "Python.h":
     ctypedef struct PyObject
diff --git a/requirements.txt b/requirements.txt
index 5d2e20b1..c2c93bf7 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1 +1,2 @@
 Cython~=3.1.2
+pytest-run-parallel[psutil]
diff --git a/test/test_buffer.py b/test/test_buffer.py
index 2c5a14c5..ca097222 100644
--- a/test/test_buffer.py
+++ b/test/test_buffer.py
@@ -17,7 +17,7 @@ def test_unpack_bytearray():
     obj = unpackb(buf, use_list=1)
     assert [b"foo", b"bar"] == obj
     expected_type = bytes
-    assert all(type(s) == expected_type for s in obj)
+    assert all(type(s) is expected_type for s in obj)
 
 
 def test_unpack_memoryview():
@@ -26,7 +26,7 @@ def test_unpack_memoryview():
     obj = unpackb(view, use_list=1)
     assert [b"foo", b"bar"] == obj
     expected_type = bytes
-    assert all(type(s) == expected_type for s in obj)
+    assert all(type(s) is expected_type for s in obj)
 
 
 def test_packer_getbuffer():
diff --git a/test/test_multithreading.py b/test/test_multithreading.py
new file mode 100644
index 00000000..6694fdc6
--- /dev/null
+++ b/test/test_multithreading.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+import threading
+from concurrent.futures import ThreadPoolExecutor
+
+from msgpack import Packer
+
+
+def run_threaded(
+    func,
+    num_threads=8,
+    pass_count=False,
+    pass_barrier=False,
+    outer_iterations=1,
+    prepare_args=None,
+):
+    """Runs a function many times in parallel"""
+    for _ in range(outer_iterations):
+        with ThreadPoolExecutor(max_workers=num_threads) as tpe:
+            if prepare_args is None:
+                args = []
+            else:
+                args = prepare_args()
+            if pass_barrier:
+                barrier = threading.Barrier(num_threads)
+                args.append(barrier)
+            if pass_count:
+                all_args = [(func, i, *args) for i in range(num_threads)]
+            else:
+                all_args = [(func, *args) for i in range(num_threads)]
+            try:
+                futures = []
+                for arg in all_args:
+                    futures.append(tpe.submit(*arg))
+            finally:
+                if len(futures) < num_threads and pass_barrier:
+                    barrier.abort()
+            for f in futures:
+                f.result()
+
+
+def test_multithread_packing():
+    output = []
+    test_data = "abcd" * 10_000_000
+    packer = Packer()
+
+    def closure(b):
+        data = packer.pack(test_data)
+        output.append(data)
+        b.wait()
+
+    run_threaded(closure, num_threads=10, pass_barrier=True, pass_count=False)
