diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown
--- a/CHANGELOG.markdown
+++ b/CHANGELOG.markdown
@@ -7,6 +7,36 @@
 *de facto* standard Haskell versioning scheme.
 
 
+1.3.0.0
+-------
+
+- **Date**    2017-12-23
+- **Git tag** [concurrency-1.3.0.0][]
+- **Hackage** https://hackage.haskell.org/package/concurrency-1.3.0.0
+
+### Control.Concurrent.Classy.Async
+
+- New `asyncBound`, `asyncBoundN`, `withAsyncBound`, and `withAsyncBoundN` functions for doing
+  asynchronous actions on bound threads. (#126)
+
+### Control.Monad.Conc.Class
+
+- `MonadConc` now supports bound threads with new `forkOS`, `forkOSN`, and `isCurrentThreadBound`
+  functions. (#126)
+
+- New `runInBoundThread` and `runInUnboundThread` functions. (#126)
+
+- The `rtsSupportsBoundThreads` definition is now the definition from Control.Concurrent
+  re-exported, not just `False`. (#126)
+
+Note that bound threads are only supported if you compile with GHC and link with -threaded.
+
+[concurrency-1.3.0.0]: https://github.com/barrucadu/dejafu/releases/tag/concurrency-1.3.0.0
+
+
+---------------------------------------------------------------------------------------------------
+
+
 1.2.3.0
 -------
 
diff --git a/Control/Concurrent/Classy/Async.hs b/Control/Concurrent/Classy/Async.hs
--- a/Control/Concurrent/Classy/Async.hs
+++ b/Control/Concurrent/Classy/Async.hs
@@ -1,7 +1,15 @@
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE RankNTypes #-}
 
--- | This module is a version of the
+-- |
+-- Module      : Control.Concurrent.Classy.Async
+-- Copyright   : (c) 2016--2017 Michael Walker
+-- License     : MIT
+-- Maintainer  : Michael Walker <mike@barrucadu.co.uk>
+-- Stability   : stable
+-- Portability : CPP, RankNTypes
+--
+-- This module is a version of the
 -- <https://hackage.haskell.org/package/async async> package. It
 -- provides a set of operations for running @MonadConc@ operations
 -- asynchronously and waiting for their results.
@@ -18,14 +26,9 @@
 -- The 'withAsync' function starts an operation in a separate thread,
 -- and kills it if the inner action finishes before it completes.
 --
--- There are a few deviations from the regular async package:
---
---   * 'asyncBound' and 'withAsyncBound' are missing as @MonadConc@
---   does not support bound threads.
---
---   * The @Alternative@ instance for 'Concurrently' uses @forever
---   yield@ in the definition of @empty@, rather than @forever
---   (threadDelay maxBound)@.
+-- Unlike the regular async package, the @Alternative@ instance for
+-- 'Concurrently' uses @forever yield@ in the definition of @empty@,
+-- rather than @forever (threadDelay maxBound)@.
 module Control.Concurrent.Classy.Async
   ( -- * Asynchronous actions
     Async
@@ -33,6 +36,8 @@
   -- * Spawning
   , async
   , asyncN
+  , asyncBound
+  , asyncBoundN
   , asyncOn
   , asyncOnN
   , asyncWithUnmask
@@ -43,6 +48,8 @@
   -- * Spawning with automatic 'cancel'ation
   , withAsync
   , withAsyncN
+  , withAsyncBound
+  , withAsyncBoundN
   , withAsyncOn
   , withAsyncOnN
   , withAsyncWithUnmask
@@ -192,6 +199,19 @@
 asyncN :: MonadConc m => String -> m a -> m (Async m a)
 asyncN name = asyncUsing (forkN name)
 
+-- | Like 'async' but uses 'forkOS' internally.
+--
+-- @since 1.3.0.0
+asyncBound :: MonadConc m => m a -> m (Async m a)
+asyncBound = asyncUsing forkOS
+
+-- | Like 'asyncBound', but using a named thread for better debugging
+-- information.
+--
+-- @since 1.3.0.0
+asyncBoundN :: MonadConc m => String -> m a -> m (Async m a)
+asyncBoundN name = asyncUsing (forkOSN name)
+
 -- | Like 'async' but using 'forkOn' internally.
 --
 -- @since 1.1.1.0
@@ -265,6 +285,19 @@
 -- @since 1.2.3.0
 withAsyncN :: MonadConc m => String -> m a -> (Async m a -> m b) -> m b
 withAsyncN name = withAsyncUsing (forkN name)
+
+-- | Like 'withAsync' but uses 'forkOS' internally.
+--
+-- @since 1.3.0.0
+withAsyncBound :: MonadConc m => m a -> (Async m a -> m b) -> m b
+withAsyncBound = withAsyncUsing forkOS
+
+-- | Like 'withAsyncBound' but using a named thread for better
+-- debugging information.
+--
+-- @since 1.3.0.0
+withAsyncBoundN :: MonadConc m => String -> m a -> (Async m a -> m b) -> m b
+withAsyncBoundN name = withAsyncUsing (forkOSN name)
 
 -- | Like 'withAsync' but uses 'forkOn' internally.
 --
diff --git a/Control/Concurrent/Classy/CRef.hs b/Control/Concurrent/Classy/CRef.hs
--- a/Control/Concurrent/Classy/CRef.hs
+++ b/Control/Concurrent/Classy/CRef.hs
@@ -48,28 +48,28 @@
   --
   -- We can see this by testing with different memory models:
   --
-  -- > > autocheck' SequentialConsistency crefs
-  -- > [pass] Never Deadlocks (checked: 6)
-  -- > [pass] No Exceptions (checked: 6)
-  -- > [fail] Consistent Result (checked: 5)
-  -- >         (False,True) S0-------S1-----S0--S2-----S0---
-  -- >         (True,False) S0-------S1-P2-----S1----S0----
-  -- >         (True,True) S0-------S1--P2-----S1---S0----
-  -- >         (False,True) S0-------S1---P2-----S1--S0----
-  -- >         (True,False) S0-------S2-----S1-----S0----
-  -- >         ...
+  -- > > autocheckWay defaultWay SequentialConsistency relaxed
+  -- > [pass] Never Deadlocks
+  -- > [pass] No Exceptions
+  -- > [fail] Consistent Result
+  -- >        (False,True) S0---------S1----S0--S2----S0--
+  -- >
+  -- >        (True,True) S0---------S1-P2----S1---S0---
+  -- >
+  -- >        (True,False) S0---------S2----S1----S0---
   -- > False
   --
-  -- > > autocheck' TotalStoreOrder crefs
-  -- > [pass] Never Deadlocks (checked: 303)
-  -- > [pass] No Exceptions (checked: 303)
-  -- > [fail] Consistent Result (checked: 302)
-  -- >         (False,True) S0-------S1-----C-S0--S2-----C-S0---
-  -- >         (True,False) S0-------S1-P2-----C-S1----S0----
-  -- >         (True,True) S0-------S1-P2--C-S1----C-S0--S2---S0---
-  -- >         (False,True) S0-------S1-P2--P1--C-C-S1--S0--S2---S0---
-  -- >         (False,False) S0-------S1-P2--P1----S2---C-C-S0----
-  -- >         ...
+  -- > > autocheckWay defaultWay TotalStoreOrder  relaxed
+  -- > [pass] Never Deadlocks
+  -- > [pass] No Exceptions
+  -- > [fail] Consistent Result
+  -- >         (False,True) S0---------S1----S0--S2----S0--
+  -- >
+  -- >         (False,False) S0---------S1--P2----S1--S0---
+  -- >
+  -- >         (True,False) S0---------S2----S1----S0---
+  -- >
+  -- >         (True,True) S0---------S1-C-S2----S1---S0---
   -- > False
   --
   -- Traces for non-sequentially-consistent memory models show where
diff --git a/Control/Monad/Conc/Class.hs b/Control/Monad/Conc/Class.hs
--- a/Control/Monad/Conc/Class.hs
+++ b/Control/Monad/Conc/Class.hs
@@ -8,7 +8,7 @@
 
 -- |
 -- Module      : Control.Monad.Conc.Class
--- Copyright   : (c) 2016 Michael Walker
+-- Copyright   : (c) 2016--2017 Michael Walker
 -- License     : MIT
 -- Maintainer  : Michael Walker <mike@barrucadu.co.uk>
 -- Stability   : experimental
@@ -17,16 +17,16 @@
 -- This module captures in a typeclass the interface of concurrency
 -- monads.
 --
--- __Deviations:__ An instance of @MonadConc@ is not required to be
--- an instance of @MonadFix@, unlike @IO@. The @CRef@, @MVar@, and
+-- __Deviations:__ An instance of @MonadConc@ is not required to be an
+-- instance of @MonadFix@, unlike @IO@. The @CRef@, @MVar@, and
 -- @Ticket@ types are not required to be instances of @Show@ or @Eq@,
 -- unlike their normal counterparts. The @threadCapability@,
 -- @threadWaitRead@, @threadWaitWrite@, @threadWaitReadSTM@,
 -- @threadWaitWriteSTM@, and @mkWeakThreadId@ functions are not
 -- provided. The @threadDelay@ function is not required to delay the
--- thread, merely to yield it. Bound threads are not supported. The
--- @BlockedIndefinitelyOnMVar@ (and similar) exceptions are /not/
--- thrown during testing, so do not rely on them at all.
+-- thread, merely to yield it. The @BlockedIndefinitelyOnMVar@ (and
+-- similar) exceptions are /not/ thrown during testing, so do not rely
+-- on them at all.
 module Control.Monad.Conc.Class
   ( MonadConc(..)
 
@@ -35,18 +35,31 @@
   , forkFinally
   , killThread
 
+  -- ** Bound threads
+
+  -- | Support for multiple operating system threads and bound threads
+  -- as described below is currently only available in the GHC runtime
+  -- system if you use the -threaded option when linking.
+  --
+  -- Other Haskell systems do not currently support multiple operating
+  -- system threads.
+  --
+  -- A bound thread is a haskell thread that is bound to an operating
+  -- system thread. While the bound thread is still scheduled by the
+  -- Haskell run-time system, the operating system thread takes care
+  -- of all the foreign calls made by the bound thread.
+  --
+  -- To a foreign library, the bound thread will look exactly like an
+  -- ordinary operating system thread created using OS functions like
+  -- pthread_create or CreateThread.
+  , IO.rtsSupportsBoundThreads
+  , runInBoundThread
+  , runInUnboundThread
+
   -- ** Named Threads
   , forkN
   , forkOnN
 
-  -- ** Bound Threads
-
-  -- | @MonadConc@ does not support bound threads, if you need that
-  -- sort of thing you will have to use regular @IO@.
-
-  , rtsSupportsBoundThreads
-  , isCurrentThreadBound
-
   -- * Exceptions
   , throw
   , catch
@@ -109,7 +122,7 @@
 -- Every @MonadConc@ has an associated 'MonadSTM', transactions of
 -- which can be run atomically.
 --
--- @since 1.0.0.0
+-- @since 1.3.0.0
 class ( Applicative m, Monad m
       , MonadCatch m, MonadThrow m, MonadMask m
       , MonadSTM (STM m)
@@ -118,6 +131,8 @@
   {-# MINIMAL
         (forkWithUnmask | forkWithUnmaskN)
       , (forkOnWithUnmask | forkOnWithUnmaskN)
+      , (forkOS | forkOSN)
+      , isCurrentThreadBound
       , getNumCapabilities
       , setNumCapabilities
       , myThreadId
@@ -194,10 +209,6 @@
   -- | Like 'forkWithUnmask', but the thread is given a name which may
   -- be used to present more useful debugging information.
   --
-  -- If an empty name is given, the @ThreadId@ is used. If names
-  -- conflict, successive threads with the same name are given a
-  -- numeric suffix, counting up from 1.
-  --
   -- > forkWithUnmaskN _ = forkWithUnmask
   --
   -- @since 1.0.0.0
@@ -234,6 +245,35 @@
   forkOnWithUnmaskN :: String -> Int -> ((forall a. m a -> m a) -> m ()) -> m (ThreadId m)
   forkOnWithUnmaskN _ = forkOnWithUnmask
 
+  -- | Fork a computation to happen in a /bound thread/, which is
+  -- necessary if you need to call foreign (non-Haskell) libraries
+  -- that make use of thread-local state, such as OpenGL.
+  --
+  -- > forkOS = forkOSN ""
+  --
+  -- @since 1.3.0.0
+  forkOS :: m () -> m (ThreadId m)
+  forkOS = forkOSN ""
+
+  -- | Like 'forkOS', but the thread is given a name which may be used
+  -- to present more useful debugging information.
+  --
+  -- > forkOSN _ = forkOS
+  --
+  -- @since 1.3.0.0
+  forkOSN :: String -> m () -> m (ThreadId m)
+  forkOSN _ = forkOS
+
+  -- | Returns 'True' if the calling thread is bound, that is, if it
+  -- is safe to use foreign libraries that rely on thread-local state
+  -- from the calling thread.
+  --
+  -- This will always be false if your program is not compiled with
+  -- the threaded runtime.
+  --
+  -- @since 1.3.0.0
+  isCurrentThreadBound :: m Bool
+
   -- | Get the number of Haskell threads that can run simultaneously.
   --
   -- @since 1.0.0.0
@@ -249,8 +289,7 @@
   -- @since 1.0.0.0
   myThreadId :: m (ThreadId m)
 
-  -- | Allows a context-switch to any other currently runnable thread
-  -- (if any).
+  -- | Allows a context-switch to any other unblocked thread (if any).
   --
   -- @since 1.0.0.0
   yield :: m ()
@@ -279,10 +318,6 @@
   -- | Create a new empty @MVar@, but it is given a name which may be
   -- used to present more useful debugging information.
   --
-  -- If an empty name is given, a counter starting from 0 is used. If
-  -- names conflict, successive @MVar@s with the same name are given a
-  -- numeric suffix, counting up from 1.
-  --
   -- > newEmptyMVarN _ = newEmptyMVar
   --
   -- @since 1.0.0.0
@@ -343,10 +378,6 @@
   -- | Create a new reference, but it is given a name which may be
   -- used to present more useful debugging information.
   --
-  -- If an empty name is given, a counter starting from 0 is used. If
-  -- names conflict, successive @CRef@s with the same name are given a
-  -- numeric suffix, counting up from 1.
-  --
   -- > newCRefN _ = newCRef
   --
   -- @since 1.0.0.0
@@ -479,10 +510,6 @@
 -- | Like 'fork', but the thread is given a name which may be used to
 -- present more useful debugging information.
 --
--- If no name is given, the @ThreadId@ is used. If names conflict,
--- successive threads with the same name are given a numeric suffix,
--- counting up from 1.
---
 -- @since 1.0.0.0
 forkN :: MonadConc m => String -> m () -> m (ThreadId m)
 forkN name ma = forkWithUnmaskN name (const ma)
@@ -490,28 +517,58 @@
 -- | Like 'forkOn', but the thread is given a name which may be used
 -- to present more useful debugging information.
 --
--- If no name is given, the @ThreadId@ is used. If names conflict,
--- successive threads with the same name are given a numeric suffix,
--- counting up from 1.
---
 -- @since 1.0.0.0
 forkOnN :: MonadConc m => String -> Int -> m () -> m (ThreadId m)
 forkOnN name i ma = forkOnWithUnmaskN name i (const ma)
 
--- Bound Threads
-
--- | Provided for compatibility, always returns 'False'.
+-- | Run the computation passed as the first argument.  If the calling
+-- thread is not /bound/, a bound thread is created temporarily.
+-- @runInBoundThread@ doesn't finish until the inner computation
+-- finishes.
 --
--- @since 1.0.0.0
-rtsSupportsBoundThreads :: Bool
-rtsSupportsBoundThreads = False
+-- You can wrap a series of foreign function calls that rely on
+-- thread-local state with @runInBoundThread@ so that you can use them
+-- without knowing whether the current thread is /bound/.
+--
+-- @since 1.3.0.0
+runInBoundThread :: MonadConc m => m a -> m a
+runInBoundThread =
+  runInThread (not <$> isCurrentThreadBound) (forkOSN "runInBoundThread")
 
--- | Provided for compatibility, always returns 'False'.
+-- | Run the computation passed as the first argument. If the calling
+-- thread is /bound/, an unbound thread is created temporarily using
+-- @fork@.  @runInBoundThread@ doesn't finish until the inner
+-- computation finishes.
 --
--- @since 1.0.0.0
-isCurrentThreadBound :: MonadConc m => m Bool
-isCurrentThreadBound = pure False
+-- Use this function /only/ in the rare case that you have actually
+-- observed a performance loss due to the use of bound threads. A
+-- program that doesn't need its main thread to be bound and makes
+-- /heavy/ use of concurrency (e.g. a web server), might want to wrap
+-- its @main@ action in @runInUnboundThread@.
+--
+-- Note that exceptions which are thrown to the current thread are
+-- thrown in turn to the thread that is executing the given
+-- computation. This ensures there's always a way of killing the
+-- forked thread.
+--
+-- @since 1.3.0.0
+runInUnboundThread :: MonadConc m => m a -> m a
+runInUnboundThread =
+  runInThread isCurrentThreadBound (forkN "runInUnboundThread")
 
+-- | Helper for 'runInBoundThread' and 'runInUnboundThread'
+runInThread :: MonadConc m => m Bool -> (m () -> m (ThreadId m)) -> m a -> m a
+runInThread check dofork action = do
+  flag <- check
+  if flag
+    then do
+      mv <- newEmptyMVar
+      mask $ \restore -> do
+        tid <- dofork $ Ca.try (restore action) >>= putMVar mv
+        let wait = takeMVar mv `catch` \(e :: SomeException) -> throwTo tid e >> wait
+        wait >>= either (\(e :: SomeException) -> throw e) pure
+    else action
+
 -- Exceptions
 
 -- | Throw an exception. This will \"bubble up\" looking for an
@@ -575,10 +632,6 @@
 -- | Create a new @MVar@ containing a value, but it is given a name
 -- which may be used to present more useful debugging information.
 --
--- If no name is given, a counter starting from 0 is used. If names
--- conflict, successive @MVar@s with the same name are given a numeric
--- suffix, counting up from 1.
---
 -- @since 1.0.0.0
 newMVarN :: MonadConc m => String -> a -> m (MVar m a)
 newMVarN n a = do
@@ -620,10 +673,15 @@
 
   fork   = IO.forkIO
   forkOn = IO.forkOn
+  forkOS = IO.forkOS
 
   forkWithUnmask   = IO.forkIOWithUnmask
   forkOnWithUnmask = IO.forkOnWithUnmask
 
+  forkOSN n ma = forkOS $ do
+    labelMe n
+    ma
+
   forkWithUnmaskN n ma = forkWithUnmask $ \umask -> do
     labelMe n
     ma umask
@@ -632,6 +690,8 @@
     labelMe n
     ma umask
 
+  isCurrentThreadBound = IO.isCurrentThreadBound
+
   getNumCapabilities = IO.getNumCapabilities
   setNumCapabilities = IO.setNumCapabilities
   readMVar           = IO.readMVar
@@ -698,11 +758,16 @@
   fork     ma = toIsConc (fork     $ unIsConc ma)
   forkOn i ma = toIsConc (forkOn i $ unIsConc ma)
 
+  forkOS    ma = toIsConc (forkOS    $ unIsConc ma)
+  forkOSN n ma = toIsConc (forkOSN n $ unIsConc ma)
+
   forkWithUnmask        ma = toIsConc (forkWithUnmask        (\umask -> unIsConc $ ma (\mx -> toIsConc (umask $ unIsConc mx))))
   forkWithUnmaskN   n   ma = toIsConc (forkWithUnmaskN   n   (\umask -> unIsConc $ ma (\mx -> toIsConc (umask $ unIsConc mx))))
   forkOnWithUnmask    i ma = toIsConc (forkOnWithUnmask    i (\umask -> unIsConc $ ma (\mx -> toIsConc (umask $ unIsConc mx))))
   forkOnWithUnmaskN n i ma = toIsConc (forkOnWithUnmaskN n i (\umask -> unIsConc $ ma (\mx -> toIsConc (umask $ unIsConc mx))))
 
+  isCurrentThreadBound = toIsConc isCurrentThreadBound
+
   getNumCapabilities = toIsConc getNumCapabilities
   setNumCapabilities = toIsConc . setNumCapabilities
   myThreadId         = toIsConc myThreadId
@@ -744,11 +809,16 @@
                                                                  \
   fork   = liftedF F fork                                      ; \
   forkOn = liftedF F . forkOn                                  ; \
+  forkOS = liftedF F forkOS                                    ; \
                                                                  \
+  forkOSN = liftedF F . forkOSN                                ; \
+                                                                 \
   forkWithUnmask        = liftedFork F forkWithUnmask          ; \
   forkWithUnmaskN   n   = liftedFork F (forkWithUnmaskN   n  ) ; \
   forkOnWithUnmask    i = liftedFork F (forkOnWithUnmask    i) ; \
   forkOnWithUnmaskN n i = liftedFork F (forkOnWithUnmaskN n i) ; \
+                                                                 \
+  isCurrentThreadBound = lift isCurrentThreadBound             ; \
                                                                  \
   getNumCapabilities = lift getNumCapabilities                 ; \
   setNumCapabilities = lift . setNumCapabilities               ; \
diff --git a/Control/Monad/STM/Class.hs b/Control/Monad/STM/Class.hs
--- a/Control/Monad/STM/Class.hs
+++ b/Control/Monad/STM/Class.hs
@@ -5,7 +5,7 @@
 
 -- |
 -- Module      : Control.Monad.STM.Class
--- Copyright   : (c) 2016 Michael Walker
+-- Copyright   : (c) 2016--2017 Michael Walker
 -- License     : MIT
 -- Maintainer  : Michael Walker <mike@barrucadu.co.uk>
 -- Stability   : experimental
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2015, Michael Walker <mike@barrucadu.co.uk>
+Copyright (c) 2016--2017, Michael Walker <mike@barrucadu.co.uk>
 
 Permission is hereby granted, free of charge, to any person obtaining
 a copy of this software and associated documentation files (the
diff --git a/README.markdown b/README.markdown
--- a/README.markdown
+++ b/README.markdown
@@ -3,16 +3,15 @@
 
 A typeclass abstraction over much of Control.Concurrent (and some
 extras!). If you're looking for a general introduction to Haskell
-concurrency, you should check out the excellent
-[Parallel and Concurrent Programming in Haskell][parconc], by Simon
-Marlow. If you are already familiar with concurrent Haskell, just
-change all the imports from Control.Concurrent.* to
-Control.Concurrent.Classy.* and fix the type errors.
+concurrency, you should check out the excellent [Parallel and
+Concurrent Programming in Haskell][parconc], by Simon Marlow. If you
+are already familiar with concurrent Haskell, just change all the
+imports from Control.Concurrent.* to Control.Concurrent.Classy.* and
+fix the type errors.
 
 A brief list of supported functionality:
 
-- Threads: the `forkIO*` and `forkOn*` functions, although bound
-  threads are not supported.
+- Threads: both unbound and bound.
 - Getting and setting capablities.
 - Yielding and delaying.
 - Mutable state: STM, `MVar`, and `IORef`.
diff --git a/concurrency.cabal b/concurrency.cabal
--- a/concurrency.cabal
+++ b/concurrency.cabal
@@ -2,7 +2,7 @@
 -- documentation, see http://haskell.org/cabal/users-guide/
 
 name:                concurrency
-version:             1.2.3.0
+version:             1.3.0.0
 synopsis:            Typeclasses, functions, and data types for concurrency and STM.
 
 description:
@@ -19,7 +19,7 @@
 license-file:        LICENSE
 author:              Michael Walker
 maintainer:          mike@barrucadu.co.uk
--- copyright:           
+copyright:           (c) 2016--2017 Michael Walker
 category:            Concurrency
 build-type:          Simple
 extra-source-files:  README.markdown CHANGELOG.markdown
@@ -32,7 +32,7 @@
 source-repository this
   type:     git
   location: https://github.com/barrucadu/dejafu.git
-  tag:      concurrency-1.2.3.0
+  tag:      concurrency-1.3.0.0
 
 library
   exposed-modules:     Control.Monad.Conc.Class
