当前位置:嗨网首页>书籍在线阅读

27-Pthread互斥

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

7.7.9 Pthread互斥

回顾7.6.1小节,确保相互排斥的最主要方法是互斥(mutex)。尽管互斥功能强大而且很重要,但实际上其使用是非常容易的。

初始化互斥

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

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

对互斥加锁

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

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

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

EDEADLK

调用线程已经持有请求的互斥体。默认情况下,不会有错误码,尝试获取已经持有的互斥体会导致死锁(参见7.6.2节)。

EINVAL

由mutex指向的互斥体是非法的。

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

327.png

对互斥解锁

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

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

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

EINVAL

由mutex指向的互斥体是无效的。

EPERM

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

对于解锁,用户也一般不会检查返回值:

329.png

Scoped锁

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

330.png 为了使用互斥,只需要调用ScopedMutex m(mutex)。当m超出作用域空间时,会自动释放锁。这使得函数比较松弛,错误处理更简单,而且可以随意使用goto语句。

Mutex示例

我们一起来看个简单的代码片段,它利用互斥来保证同步。回顾一下第7.5节中的“现实世界中的竞争场景”中的银行取款例子。我们虚构的银行面临严重的条件竞争问题,允许不应该支持的行为。通过Pthread互斥体,我们可以解决取款问题:

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

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

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

332.png