diff --git a/src/ZooKeeper/Recipe.hs b/src/ZooKeeper/Recipe.hs
new file mode 100644
--- /dev/null
+++ b/src/ZooKeeper/Recipe.hs
@@ -0,0 +1,7 @@
+module ZooKeeper.Recipe
+  ( module ZooKeeper.Recipe.Election
+  , module ZooKeeper.Recipe.Lock
+  ) where
+
+import           ZooKeeper.Recipe.Election
+import           ZooKeeper.Recipe.Lock
diff --git a/src/ZooKeeper/Recipe/Election.hs b/src/ZooKeeper/Recipe/Election.hs
--- a/src/ZooKeeper/Recipe/Election.hs
+++ b/src/ZooKeeper/Recipe/Election.hs
@@ -1,4 +1,6 @@
-module ZooKeeper.Recipe.Election where
+module ZooKeeper.Recipe.Election
+  ( election
+  ) where
 
 import           Control.Monad
 import qualified Z.Data.Builder         as B
diff --git a/src/ZooKeeper/Recipe/Lock.hs b/src/ZooKeeper/Recipe/Lock.hs
new file mode 100644
--- /dev/null
+++ b/src/ZooKeeper/Recipe/Lock.hs
@@ -0,0 +1,62 @@
+module ZooKeeper.Recipe.Lock
+  ( lock
+  , unlock
+  ) where
+
+import           Control.Exception
+import           Control.Monad
+import           Z.Data.CBytes          (CBytes)
+
+import           ZooKeeper
+import           ZooKeeper.Exception
+import           ZooKeeper.Recipe.Utils (SequenceNumWithGUID (..),
+                                         createSeqEphemeralZNode,
+                                         mkSequenceNumWithGUID)
+import           ZooKeeper.Types
+
+-- | To acquire a distributed lock.
+lock :: ZHandle
+     -- ^ The zookeeper handle obtained by a call to 'zookeeperResInit'
+     -> CBytes
+     -- ^ The path to get the lock. Ephemeral znodes will be put on it
+     -> CBytes
+     -- ^ The GUID for this zookeeper session. To handle recoverable execptions
+     -- correctly, it should be distinct from different sessions
+     -> IO CBytes
+     -- ^ The real path of the lock that acquired. It will be used when unlocking
+     -- the same lock
+lock zk lockPath guid = do
+  -- Check persistent paths
+  electionExists <- zooExists zk lockPath
+  case electionExists of
+    Just _  -> return ()
+    Nothing -> void (try $ zooCreate zk lockPath Nothing zooOpenAclUnsafe ZooPersistent :: IO (Either ZooException StringCompletion))
+
+  -- Create Ephemeral and Sequece znode, and get the seq number i
+  (StringCompletion this) <- createSeqEphemeralZNode zk lockPath guid
+  let thisSeqNumWithGUID = mkSequenceNumWithGUID this
+
+  callback zk thisSeqNumWithGUID
+  where
+    callback zk_ self = do
+      (StringsCompletion (StringVector children)) <- zooGetChildren zk_ lockPath
+      let childrenSeqNumWithGUID = mkSequenceNumWithGUID <$> children
+      case filter (< self) childrenSeqNumWithGUID of
+        [] -> do
+          return $ unSequenceNumWithGUID self
+        xs -> do
+          let toWatch = lockPath <> "/" <> unSequenceNumWithGUID (maximum xs)
+          zooWatchExists zk_ toWatch (\HsWatcherCtx{..} -> void $ callback watcherCtxZHandle self)
+            (\_ -> return ())
+          return $ unSequenceNumWithGUID self
+
+-- | To release a distributed lock. Note that the real lock path
+-- should be the one acquired by 'lock', otherwise, a 'ZooException'
+-- will be thrown.
+unlock :: ZHandle
+       -- ^ The zookeeper handle obtained by a call to 'zookeeperResInit'
+       -> CBytes
+       -- ^ The real lock path acquired by 'lock'. An exception will be
+       -- thrown if it is bad (for example, does not exist)
+       -> IO ()
+unlock zk thisLock = zooDelete zk thisLock Nothing
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -3,17 +3,18 @@
 module Main where
 
 import           Control.Concurrent
-import           Control.Monad             (replicateM, void)
-import qualified Data.UUID                 as UUID
+import           Control.Concurrent.Async (concurrently_)
+import           Control.Monad            (replicateM, void)
+import qualified Data.UUID                as UUID
 import           Data.UUID.V4
-import           Data.Version              (makeVersion)
+import           Data.Version             (makeVersion)
 import           Foreign.C
 import           Test.Hspec
-import qualified Z.Data.CBytes             as CB
+import qualified Z.Data.CBytes            as CB
 
 import           ZooKeeper
 import           ZooKeeper.Exception
-import           ZooKeeper.Recipe.Election (election)
+import           ZooKeeper.Recipe         (election, lock, unlock)
 import           ZooKeeper.Types
 
 recvTimeout :: CInt
@@ -29,6 +30,8 @@
   hspec $ propSpec zh
   hspec $ electionSpec1 zh
   hspec $ electionSpec2 client
+  hspec $ lockSpec1 zh
+  hspec $ lockSpec2 client
 
 opSpec :: ZHandle -> Spec
 opSpec zh = do
@@ -174,6 +177,43 @@
     killThread t1
     takeMVar finished4
     readMVar leader `shouldReturn` (2 :: Int)
+
+lockSpec1 :: ZHandle -> Spec
+lockSpec1 zh = describe "ZooKeeper.zooLock (single session)" $ do
+  it "Test single client situation" $ do
+    result <- newEmptyMVar
+    uuid <- nextRandom
+    thisLock <- lock zh "/lock1" (CB.pack . UUID.toString $ uuid)
+    putMVar result 1
+    unlock zh thisLock
+    readMVar result `shouldReturn` (1 :: Int)
+
+lockSpec2 :: Resource ZHandle -> Spec
+lockSpec2 client_ = describe "ZooKeeper.zooLock (multi sessions)" $ do
+  it "Test multi clients situation" $ do
+    result1 <- newMVar 0
+    result2 <- newMVar 0
+    [uuid1, uuid2] <- replicateM 2 nextRandom
+
+    concurrently_ (action1 result1) (action1 result1)
+    readMVar result1 `shouldReturn` (1 :: Int)
+
+    withResource client_ $ \zh1 -> do
+      withResource client_ $ \zh2 -> do
+        concurrently_ (action2 zh1 (CB.pack . UUID.toString $ uuid1) result2)
+                      (action2 zh2 (CB.pack . UUID.toString $ uuid2) result2)
+        readMVar result2 `shouldReturn` (2 :: Int)
+  where
+    action1 target = do
+      x <- readMVar target
+      threadDelay 100000
+      swapMVar target (x + 1)
+    action2 zh guid target = do
+      thisLock <- lock zh "/lock2" guid
+      x <- readMVar target
+      threadDelay 100000
+      _ <- swapMVar target (x + 1)
+      unlock zh thisLock
 
 -------------------------------------------------------------------------------
 
diff --git a/zoovisitor.cabal b/zoovisitor.cabal
--- a/zoovisitor.cabal
+++ b/zoovisitor.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.2
 name:               zoovisitor
-version:            0.1.4.0
+version:            0.1.5.0
 synopsis:
   A haskell binding to Apache Zookeeper C library(mt) using Haskell Z project.
 
@@ -32,12 +32,14 @@
   exposed-modules:
     ZooKeeper
     ZooKeeper.Exception
-    ZooKeeper.Recipe.Election
+    ZooKeeper.Recipe
     ZooKeeper.Types
 
   other-modules:
     ZooKeeper.Internal.FFI
     ZooKeeper.Internal.Types
+    ZooKeeper.Recipe.Election
+    ZooKeeper.Recipe.Lock
     ZooKeeper.Recipe.Utils
 
   build-depends:
@@ -80,6 +82,7 @@
   main-is:          Spec.hs
   hs-source-dirs:   test
   build-depends:
+    , async
     , base        >=4.12 && <5
     , hspec
     , uuid
