diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,10 @@
 # Changelog for unliftio
 
+## 0.2.14
+
+* Add `UnliftIO.QSem`
+* Add `UnliftIO.QSemN`
+
 ## 0.2.13.1
 
 * Improve `UnliftIO.Exception` documentation
diff --git a/src/UnliftIO.hs b/src/UnliftIO.hs
--- a/src/UnliftIO.hs
+++ b/src/UnliftIO.hs
@@ -9,6 +9,8 @@
   , module UnliftIO.IORef
   , module UnliftIO.Memoize
   , module UnliftIO.MVar
+  , module UnliftIO.QSem
+  , module UnliftIO.QSemN
   , module UnliftIO.STM
   , module UnliftIO.Temporary
   , module UnliftIO.Timeout
@@ -22,6 +24,8 @@
 import UnliftIO.IORef
 import UnliftIO.Memoize
 import UnliftIO.MVar
+import UnliftIO.QSem
+import UnliftIO.QSemN
 import UnliftIO.STM
 import UnliftIO.Temporary
 import UnliftIO.Timeout
diff --git a/src/UnliftIO/QSem.hs b/src/UnliftIO/QSem.hs
new file mode 100644
--- /dev/null
+++ b/src/UnliftIO/QSem.hs
@@ -0,0 +1,43 @@
+-- | Unlifted "Control.Concurrent.QSem".
+--
+-- @since 0.2.14
+module UnliftIO.QSem
+  ( QSem
+  , newQSem
+  , waitQSem
+  , signalQSem
+  , withQSem
+  ) where
+
+import Control.Concurrent.QSem (QSem)
+import Control.Monad.IO.Unlift
+import UnliftIO.Exception
+import qualified Control.Concurrent.QSem as Q
+
+-- | Lifted 'Q.newQSem'.
+--
+-- @since 0.2.14
+newQSem :: MonadIO m => Int -> m QSem
+newQSem = liftIO . Q.newQSem
+
+-- | Lifted 'Q.waitQSem'.
+--
+-- @since 0.2.14
+waitQSem :: MonadIO m => QSem -> m ()
+waitQSem = liftIO . Q.waitQSem
+
+-- | Lifted 'Q.signalQSem'.
+--
+-- @since 0.2.14
+signalQSem :: MonadIO m => QSem -> m ()
+signalQSem = liftIO . Q.signalQSem
+
+-- | 'withQSem' is an exception-safe wrapper for performing the
+-- provided operation while holding a unit of value from the semaphore.
+-- It ensures the semaphore cannot be leaked if there are exceptions.
+--
+-- @since 0.2.14
+{-# INLINE withQSem #-}
+withQSem :: MonadUnliftIO m => QSem -> m a -> m a
+withQSem x io = withRunInIO $ \run ->
+  bracket_ (waitQSem x) (signalQSem x) (run io)
diff --git a/src/UnliftIO/QSemN.hs b/src/UnliftIO/QSemN.hs
new file mode 100644
--- /dev/null
+++ b/src/UnliftIO/QSemN.hs
@@ -0,0 +1,43 @@
+-- | Unlifted "Control.Concurrent.QSemN".
+--
+-- @since 0.2.14
+module UnliftIO.QSemN
+  ( QSemN
+  , newQSemN
+  , waitQSemN
+  , signalQSemN
+  , withQSemN
+  ) where
+
+import Control.Concurrent.QSemN (QSemN)
+import Control.Monad.IO.Unlift
+import UnliftIO.Exception
+import qualified Control.Concurrent.QSemN as Q
+
+-- | Lifted 'Q.newQSemN'.
+--
+-- @since 0.2.14
+newQSemN :: MonadIO m => Int -> m QSemN
+newQSemN = liftIO . Q.newQSemN
+
+-- | Lifted 'Q.waitQSemN'.
+--
+-- @since 0.2.14
+waitQSemN :: MonadIO m => QSemN -> Int -> m ()
+waitQSemN x = liftIO . Q.waitQSemN x
+
+-- | Lifted 'Q.signalQSemN'.
+--
+-- @since 0.2.14
+signalQSemN :: MonadIO m => QSemN -> Int -> m ()
+signalQSemN x = liftIO . Q.signalQSemN x
+
+-- | 'withQSemN' is an exception-safe wrapper for performing the
+-- provided operation while holding N unit of value from the semaphore.
+-- It ensures the semaphore cannot be leaked if there are exceptions.
+--
+-- @since 0.2.14
+{-# INLINE withQSemN #-}
+withQSemN :: MonadUnliftIO m => QSemN -> Int -> m a -> m a
+withQSemN x n io = withRunInIO $ \run ->
+  bracket_ (waitQSemN x n) (signalQSemN x n) (run io)
diff --git a/unliftio.cabal b/unliftio.cabal
--- a/unliftio.cabal
+++ b/unliftio.cabal
@@ -4,10 +4,10 @@
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: c11215b81aa0898968a1dbf5189319f0bd07fa70861b5812ac34043c9f118ebb
+-- hash: e737146da57bc35659bcdf122bb96b8fd76e43a33e39f3ebf00fac260c68bb2b
 
 name:           unliftio
-version:        0.2.13.1
+version:        0.2.14
 synopsis:       The MonadUnliftIO typeclass for unlifting monads to IO (batteries included)
 description:    Please see the documentation and README at <https://www.stackage.org/package/unliftio>
 category:       Control
@@ -39,6 +39,8 @@
       UnliftIO.Memoize
       UnliftIO.MVar
       UnliftIO.Process
+      UnliftIO.QSem
+      UnliftIO.QSemN
       UnliftIO.STM
       UnliftIO.Temporary
       UnliftIO.Timeout
