BlueClue's Tech Blog

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

Mutex Variables(互斥量)

  • Mutex(互斥量)是“mutual exclusion”的缩写,互斥量最主要的用途是在多线程中对共享数据同进行写操作时同步线程并保护数据。
  • 互斥量在保护共享数据资源时可以把它想象成一把锁,在Pthreads库中互斥量最基本的设计思想是在任一时间只有一个线程可以锁定(或拥有)互斥量,因此,即使许多线程尝试去锁定一个互斥量时成功的只会有一个,只有在拥有互斥量的线程开锁后其它的线程才能锁定,就是说,线程必须排队访问被保护的数据。
  • 互斥量常被用来防止竞争条件(race conditions),下面是一个涉及银行业务的竞争条件案例:
Thread 1Thread 2Balance
Read balance: $1000   $1000
  Read balance: $1000 $1000
  Deposit $200 $1000
Deposit $200   $1000
Update balance $1000+$200   $1200
  Update balance $1000+$200 $1200
  • 在上面的案例中,当线程使用共享数据资源Banlance时,互斥量会锁定“Balance”,如果不锁定,就会像表内情况一样,Balance的计算结果会混乱。
  • 拥有互斥量的线程常常做的一个事情就是更改全局变量值,这是一个安全方法,确保在多个线程更改同一个变量时,它最终的值与唯一一个线程执行更改操作后的结果是一致的,这个被更改的变量属于一个临界区(critical section)。
  • 使用互斥量的一个标准的过程是:
  1. 创建并初始化互斥量;
  2. 多个线程试图锁定此互斥量;
  3. 有且只有一个线程成功的拥有了这个互斥量;
  4. 拥有互斥量的线程执行了一系列的操作;
  5. 拥有互斥量的线程解锁互斥量;
  6. 另一个线程获得此互斥量,并重复上面的过程;
  7. 最终互斥量被销毁。

 

  • 当许多线程竞争一个互斥量时,在请求时失败的线程会阻塞——无阻塞的请求是用“trylock”而不是用“lock”,所以pthread_mutex_trylock()与pthread_mutex_lock()的区别就是在互斥量还没有解锁的情况下,前者不会阻塞线程。
  • 在保护共享数据时,程序员的责任是确定每个线程需要用到互斥量,例如,如果有4个线程正在更改同一个数据,但是只有一个线程使用了互斥量,那么这个数据仍然会损坏。

 

创建并销毁互斥量

 

函数:

pthread_mutex_init (mutex,attr)

pthread_mutex_destroy (mutex)

pthread_mutexattr_init (attr)

pthread_mutexattr_destroy (attr)

 

用法:

  • 互斥量必须要声明为pthread_mutex_t类型,并且在使用之前必须要被初始化。有两种方法初始化互斥量:
  1. 静态初始化,例如:pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER;
  2. 动态初始化,使用pthread_mutex_init()过程,此函数可以设置互斥量属性对象attr;

注意,互斥量初始状态是未被锁定的(unlocked)。

 

  • 对象attr常被用来设置互斥量对象的属性值的,而且必须是pthread_mutexattr_t类型(默认值是NULL)。Pthreads标准定义了3个互斥量可设置选项:
  1. Protocol:为互斥量指定用来防止优先级倒置的规则的;
  2. Prioceiling:为互斥量指定优先级上限的;
  3. Process-shared:为互斥量指定进程共享的。

注意,不是所有的实现都要提供这三个可选的互斥量属性的。

 

  • pthread_mutexattr_init()和pthread_mutexattr_destroy()函数分别是用来创建和销毁互斥量属性的。
  • pthread_mutex_destroy()在互斥量不再被需要时,用来释放互斥量对象。

 

锁定和解锁互斥量

 

函数:

pthread_mutex_lock (mutex)

pthread_mutex_trylock (mutex)

pthread_mutex_unlock (mutex)

 

用法:

  • 线程用pthread_mutex_lock()函数通过指定的互斥量获得一个锁,如果互斥量已经被另一个线程锁定,那么这个请求会阻塞申请的线程,直到互斥量解锁。
  • pthread_mutex_trylock()尝试锁定互斥量,然而,如果互斥量已经被锁定,那么此函数立即返回一个‘busy’的错误码,这个函数在优先级反转的情况下用来防止死锁是很有用的。
  • 如果拥有锁的线程调用pthread_mutex_unlock()函数,那么将会解锁该互斥量;如果其它线程将要为处理被保护的数据请求互斥量,那么拥有该互斥量的线程在完成数据操作后调用该函数。发生下列情况,会有错误码返回:
  1. 如果互斥量已经被解锁;
  2. 如果互斥量被另一个线程拥有。
  • 关于互斥量已经再也没有任何神奇的功用了……事实上,它们很类似于所有参与的线程之间的一个“君子协定”。

 

案例分析

 

代码
/*****************************************************************************
* 描述:
* 这个例子阐明了互斥变量在线程编程中的用法
* main数据域定义一个全局的可访问结构体,所有线程均可访问,
* 每个线程都为此数据的一部分做运算工作,主线程等待所有线程完成运算,
* 并最后打印出计算结果。
*****************************************************************************
*/
#include
<pthread.h>
#include
<stdio.h>
#include
<stdlib.h>


/*下面的结构体包含了dotprod函数要处理的所需要的信息,
* 访问输入数据并把输出写入结构体
*
*/
typedef
struct {
double *a;
double *b;
double sum;//ab的和
int veclen;//结构体个数
} DOTDATA;


/*全局变量和一个互斥量*/
#define NUMTHRDS 4
#define VECLEN 100
DOTDATA dotstr;
pthread_t callThd[NUMTHRDS];
pthread_mutex_t mutexsum;


/*dotprod函数在线程创建时被激活,从DOTDATA结构体内取出数据,之后把运算结果又写入到结构体中。
* 这种处理方法的好处很明显是多线程编程:线程创建时,我们给被激活的函数传递了一个参数——参数值是线程的序号。
* 函数需要的其它信息是来自全局的结构体。
*
*/
void *dotprod(void *arg) {
int i, start, end, len;
long offset;
double mysum, *x, *y;
offset
= (long) arg;
len
= dotstr.veclen;
start
= offset * len;
end
= start + len;
x
= dotstr.a;
y
= dotstr.b;
//计算
mysum = 0;
for (i = start; i < end; i++) {
mysum
+= (x[i] * y[i]);
}


/*锁定互斥量,更改共享结构的值,解锁 */
pthread_mutex_lock(
&mutexsum);
dotstr.sum
+= mysum;
printf(
"Thread%ld: mysum=%f,dotstr.sum=%f\n",offset,mysum,dotstr.sum);
pthread_mutex_unlock(
&mutexsum);

pthread_exit((
void*) 0);
}


/*主程序创建线程,线程计算数据,然后打印出结果。在创建线程前,创建输入数据。
* 因为所有线程更改同一个结构体,所以我们需要一个互斥量。
* 主线程需要等待所有线程完成,它等待这些线程中的一个,我们为线程赋予属性,允许主线程连接(join)其它被
* 创建的线程。注意,当不再需要它们是要记得释放它们。
*
*/
int main(int argc, char *argv[]) {
long i;
double *a, *b;
void *status;
pthread_attr_t attr;

/* 初始化变量 */
a
= (double*) malloc(NUMTHRDS * VECLEN * sizeof(double));
b
= (double*) malloc(NUMTHRDS * VECLEN * sizeof(double));

for (i = 0; i < VECLEN * NUMTHRDS; i++) {
a[i]
= 1;
b[i]
= a[i];
}

dotstr.veclen
= VECLEN;
dotstr.a
= a;
dotstr.b
= b;
dotstr.sum
= 0;

pthread_mutex_init(
&mutexsum, NULL);


pthread_attr_init(
&attr);
pthread_attr_setdetachstate(
&attr, PTHREAD_CREATE_JOINABLE);

for (i = 0; i < NUMTHRDS; i++) {
/*创建线程,激活dotpod函数,传入参数线程序号 */
printf(
"i=%d\n",i);
pthread_create(
&callThd[i], &attr, dotprod, (void *) i);
}

pthread_attr_destroy(
&attr);

/* 等待其它线程 */
for (i = 0; i < NUMTHRDS; i++) {
pthread_join(callThd[i],
&status);
}

/* 线程join后,释放内存,销毁互斥量*/
printf(
"Sum = %f \n", dotstr.sum);
free(a);
free(b);
pthread_mutex_destroy(
&mutexsum);
pthread_exit(NULL);
}

 

运行结果
# Pthreads
i
=0
Thread0: mysum
=100.000000,dotstr.sum=100.000000
i
=1
Thread1: mysum
=100.000000,dotstr.sum=200.000000
i
=2
Thread2: mysum
=100.000000,dotstr.sum=300.000000
i
=3
Thread3: mysum
=100.000000,dotstr.sum=400.000000
Sum
= 400.000000

 

指针和内存的图像化

主程序申请了两块内存,各分为四块,上图标明了各个指针的指向,程序共创建四个线程,把两块4*100的内存区域分成四块,做连加运算得到mysum,之后再把所有的运算结果加起来赋值于dostr.sum,由于它是多线程共享的全局变量这里得利用互斥变量排队做加法,才能保证该值结果的正常。

<!--[if !vml]-->
<!--[endif]-->
posted on 2010-07-17 15:30  blueclue  阅读(3564)  评论(1编辑  收藏  举报