天天看点

Pthread 互斥初始化互斥 对互斥加锁 对互斥解锁 Mutex示例 Scoped锁

初始化互斥

互斥使用 pthread_mutex_t 对象表示。正如 Pthread API 中的绝大多数对象,它表示提供给各种互斥接口的模糊结构。虽然你可以动态创建互斥,绝大多数使用方式都是静态的:

                pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

上面这段代码定义和初始化了一个互斥体 mutex。在使用互斥之前,需要做只有这么多。

对互斥加锁

锁(也称为获取)Pthread 互斥是通过 pthread_mutex_lock() 函数实现的:

        #include <pthread.h>         int pthread_mutex_lock (pthread_mutex_t *mutex);

成功调用 pthread_mutex_lock() 会阻塞调用的线程,直到由 mutex 指向的互斥体变得可用。一旦互斥体可用了,调用线程就会被唤醒,函数返回 0。如果在调用时互斥体可用,函数会立即返回。

出错时,函数可能返回的非 0错误码如下:

EDEADLK   调用线程已经持有请求的互斥体。默认情况下,不会有错误码,尝试获取已经持有的互斥体会导致死                           锁。 EINVAL       有 mutex 指向的互斥体是非法的。

调用方往往不会检查返回值,因为编码风格良好的代码不应该在运行时生成错误信息。一种使用方式如下:

        pthread_mutex_lock (&mutex);

对互斥解锁

加锁的反面是解锁,或者称释放互斥体。

        #include <pthread.h>         int pthread_mutex_unlock (pthread_mutex_t *mutex);

成功调用 pthread_mutex_unlock() 会释放由 mutex 所指向的互斥体,并返回 0。该调用不会阻塞,互斥可以立即释放。

出错时,函数返回非 0 的错误码,可能的错误码如下:

EINVAL      由 mutex 指向的互斥体是无效的。 EPERM     调用进程没有持有由 mutex 指向的互斥。该错误码是不确定的,如果尝试释放一个没有持有的互斥会产                       生 bug。

对于解锁,用户一般也不会检查返回值:                  pthread_mutex_unlock (&mutex);

Mutex示例

用银行取款的问题作为例子:

static pthread_mutex_t the_mutex = PTHREAD_MUTEX_INITIALIZER;

int withdraw (struct account *account, int amount) {      pthread_mutex_lock (&the_mutex);      const int balance = account->balance;      if ( balance < amount)     {           pthread_mutex_unlock (&the_mutex);           return -1;      }      account->balance = balance - amount;      pthread_mutex_unlock (&the_mutex);

     disburse_money (amount);      return 0; }

这个例子使用 pthread_mutex_lock() 来获取一个互斥体,然后通过 pthread_mutex_unlock() 释放它。通过这种方式,有助于消除竞争条件,但是它引入了银行中单点竞争问题:一次只能有一个用户取款!这会带来很大性能瓶颈,对于一个规模很大的银行而言,这种方式非常失败。

因此,绝大多数对锁的使用都避免全局锁,而是把锁和某些数据结构的特定实例关联起来。这称为细粒度锁。它可以使得锁语义更复杂,尤其是对于死锁避免,但是它是利用现代计算机多核扩展的关键。

在这个例子中,不是定义全局锁,而是在 account结构体内定义一把锁,使得每个 account 实例都有自己的锁。由于临界区中的数据只在 account 结构体中,所以这种机制工作良好。通过只锁定借方账户,银行可以并行处理其他客户的取款操作。

int withdraw (struct account *account, int amount) {      pthread_mutex_lock (&account->mutex);      const int balance = account->balance;      if ( balance < amount)     {           pthread_mutex_unlock (&account->mutex);           return -1;      }      account->balance = balance - amount;      pthread_mutex_unlock (&account->mutex);

     disburse_money (amount);      return 0; }

Scoped锁

资源获取及初始化 (RAII)是 C++ 的一种编程模式——它是C++语言最强大的模式之一。RAII 通过把资源的生命周期绑定到一个 scoped 对象的生命周期上,高效地实现了资源分配和回收。虽然 RAII 本是为了处理异常抛出后的资源清理而设计的,它是管理资源的最强大方式之一。举个例子,RAII 支持创建一个 “Scpoed 文件”对象,当创建对象时打开文件,当对象超出范围内自动关闭。同样,我们也可以创建一个 “Scpoed 锁”,在创建获取锁时,当超出作用域空间时,自动释放互斥体。

#include <pthread.h> #include <boost/noncopyable.h>

class MutexLock : private boost::noncopyable { public:        MutexLock()        {                pthread_mutex_init (&mutex_, NULL);        }        ~MutexLock()        {                pthread_mutex_destory (&mutex_);        }        void lock ()        {              pthread_mutex_lock (&mutex_);        }         void unlock()         {               pthread_mutex_unlock(&mutex_);         }

private:       pthread_mutex_t mutex_; };

class MutexLockGuard : private boost::noncopyable { public:       explicit MutexLockGuard (MutexLock& mutex)               :mutex_(mutex)       {              mutex_.lock();       }       ~MutexLockGuard()       {               mutex_.unlock();        }

private:       MutexLock& mutex_; };