众所周知,互斥量(mutex)是同步线程对共享资源访问的技术,用来防止下面这种情况:线程A试图访问某个共享资源时,线程B正在对其进行修改,从而造成资源状态不一致。与之相关的一个术语临界区(critical section)是指访问某一共享资源的代码片段,并且这段代码的执行为原子(atomic)操作,即同时访问同一共享资源的其他线程不应中断该片段的执行。

我们先来看看不使用临界区技术保护共享资源的例子,该例子使用2个线程来同时递增同一个全局变量。

代码示例1:不使用临界区技术访问共享资源

 #include <pthread.h>
#include <stdio.h>
#include <stdlib.h> static int g_n = ; static void *
thread_routine(void *arg)
{
int n_loops = (int)(arg);
int loc;
int j; for (j = ; j < n_loops; j++)
{
loc = g_n;
loc++;
g_n = loc;
} return ;
} int
main(int argc, char *argv[])
{
int n_loops, s;
pthread_t t1, t2;
void *args[]; n_loops = (argc > ) ? atoi(argv[]) : ; args[] = (void *)n_loops;
s = pthread_create(&t1, , thread_routine, &args);
if (s != )
{
perror("error pthread_create.\n");
exit(EXIT_FAILURE);
} s = pthread_create(&t2, , thread_routine, &args);
if (s != )
{
perror("error pthread_create.\n");
exit(EXIT_FAILURE);
} s = pthread_join(t1, );
if (s != )
{
perror("error pthread_join.\n");
exit(EXIT_FAILURE);
} s = pthread_join(t2, );
if (s != )
{
perror("error pthread_join.\n");
exit(EXIT_FAILURE);
} printf("Loops [%d] times by 2 threads without critical section.\n", n_loops);
printf("Var g_n is [%d].\n", g_n);
exit(EXIT_SUCCESS);
}

运行以上代码生成的程序,若循环次数较少,比如每个线程都对全局变量g_n递增1000次,结果看起来很正常:

$ ./thdincr_nosync 1000
Loops [1000] times by 2 threads without critical section.
Var g_n is [2000].

如果加大每个线程的循环次数,结果将大不相同:

$ ./thdincr_nosync 10000000
Loops [10000000] times by 2 threads without critical section.
Var g_n is [18655665].

造成以上问题的原因在于下面的执行序列:

1. 线程1将g_n的值赋给局部变量loc。假设g_n的当前值为1000。

2. 线程1的时间片用尽,线程2开始执行。

3. 线程2执行多次循环:将g_n的值改为其他的值,例如3000,线程2的时间片用尽。

4. 线程1重新获得时间片,并从上次停止处恢复执行。线程1在上次运行时,已将g_n的值(1000)赋给loc,现在递增loc,再将loc的值1001赋给g_n。此时线程2之前递增操作的结果遭到覆盖。

如果使用上面同样的命令行参数运行该程序多次,g_n的值会出现很大波动:

$ ./thdincr_nosync 10000000
Loops [10000000] times by 2 threads without critical section.
Var g_n is [14085995].

$ ./thdincr_nosync 10000000
Loops [10000000] times by 2 threads without critical section.
Var g_n is [13590133].

$ ./thdincr_nosync 10000000
Loops [10000000] times by 2 threads without critical section.
Var g_n is [20000000].

$ ./thdincr_nosync 10000000
Loops [10000000] times by 2 threads without critical section.
Var g_n is [16550684].

这一行为结果的不确定性,原因在于内核CPU调度顺序的不可预测性。若在复杂的程序中发生这种不确定结果的行为,意味着此类错误将偶尔发作,难以复现,因此也很难发现。如果使用如下语句:

g_n++;        /* 或者: ++g_n */

来替换thread_routine内for循环中的3条语句,似乎可以解决这一问题,不过在很多硬件架构上,编译器在将这条语句转换成机器码时,其效果仍等同于原先thread_routine内for循环中的3条语句。即换成一条语句并非意味着该操作就是原子操作。

为了避免上述同一行为的结果不确定性,必须使用某种技术来确保同一时刻只有一个线程可以访问共享资源,在Linux/Unix系统中,互斥量mutex(mutual exclusion的缩写)就是为这种情况设计的一种线程间同步技术,可以使用互斥量来保证对任意共享资源的原子访问。

互斥量有两种状态:已锁定和未锁定。任何时候,至多只有一个线程可以锁定该互斥量。试图对已经锁定的互斥量再次加锁,将可能阻塞线程或者报错,具体取决于加锁时使用的方法。

静态分配的互斥量:

互斥量既可以像静态变量那样分配,也可以在运行时动态创建,例如,通过malloc在堆中分配,或者在栈上的自动变量,下面的语句展示了如何初始化静态分配的互斥量:

static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

互斥量的加锁和解锁操作:

初始化之后,互斥量处于未锁定状态。函数pthread_mutex_lock()可以锁定某一互斥量,而函数pthread_mutex_unlock()可以将一个已经锁定的互斥量解锁。

#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex); /* 两个函数在成功时返回值为0,失败时返回一个正值代表错误号。 */

代码示例2:使用静态分配的互斥量保护对全局变量的访问

 #include <pthread.h>
#include <stdio.h>
#include <stdlib.h> static int g_n = ;
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; static void *
thread_routine(void *arg)
{
int n_loops = *((int *)arg);
int loc;
int j;
int s; for (j = ; j < n_loops; j++)
{
s = pthread_mutex_lock(&mtx);
if (s != )
{
perror("error pthread_mutex_lock.\n");
exit(EXIT_FAILURE);
} loc = g_n;
loc++;
g_n = loc; s = pthread_mutex_unlock(&mtx);
if (s != )
{
perror("error pthread_mutex_unlock.\n");
exit(EXIT_FAILURE);
}
} return ;
} int
main(int argc, char *argv[])
{
pthread_t t1, t2;
int n_loops, s; n_loops = (argc > ) ? atoi(argv[]) : ; s = pthread_create(&t1, , thread_routine, &n_loops);
if (s != )
{
perror("error pthread_create.\n");
exit(EXIT_FAILURE);
} s = pthread_create(&t2, , thread_routine, &n_loops);
if (s != )
{
perror("error pthread_create.\n");
exit(EXIT_FAILURE);
} s = pthread_join(t1, );
if (s != )
{
perror("error pthread_join.\n");
exit(EXIT_FAILURE);
} s = pthread_join(t2, );
if (s != )
{
perror("error pthread_join.\n");
exit(EXIT_FAILURE);
} printf("Var g_n is [%d].\n", g_n);
exit(EXIT_SUCCESS);
}

运行此示例代码生成的程序,从结果中可以看出对g_n的递增操作总能保持正确:

$ ./thdincr_mutex 10000000
Var g_n is [20000000].
$ ./thdincr_mutex 10000000
Var g_n is [20000000].
$ ./thdincr_mutex 10000000
Var g_n is [20000000].
$ ./thdincr_mutex 10000000
Var g_n is [20000000].
$ ./thdincr_mutex 10000000
Var g_n is [20000000].

代码示例3:使用动态分配的互斥量保护对全局变量的访问

 #include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h> static int g_n = ; static void *
thread_routine(void *arg)
{
void **args = (void **)arg;
int n_loops = (int)(args[]);
int loc;
int j;
int s;
pthread_mutex_t *mtx = (pthread_mutex_t *)(args[]); for (j = ; j < n_loops; j++)
{
s = pthread_mutex_lock(mtx);
if (s != )
{
printf("error pthread_mutex_lock. return:[%d] errno:[%d]\n", s, errno);
exit(EXIT_FAILURE);
} loc = g_n;
loc++;
g_n = loc; s = pthread_mutex_unlock(mtx);
if (s != )
{
perror("error pthread_mutex_unlock.\n");
exit(EXIT_FAILURE);
}
} return ;
} int
main(int argc, char *argv[])
{
int n_loops, s;
pthread_t t1, t2;
pthread_mutex_t mtx;
pthread_mutexattr_t mtx_attr;
void *args[]; s = pthread_mutexattr_init(&mtx_attr);
if (s != )
{
perror("error pthread_mutexattr_init.\n");
exit(EXIT_FAILURE);
} s = pthread_mutexattr_settype(&mtx_attr, PTHREAD_MUTEX_ERRORCHECK);
if (s != )
{
perror("error pthread_mutexattr_settype.\n");
exit(EXIT_FAILURE);
} s = pthread_mutex_init(&mtx, &mtx_attr);
if (s != )
{
perror("error pthread_mutex_init.\n");
exit(EXIT_FAILURE);
} s = pthread_mutexattr_destroy(&mtx_attr);
if (s != )
{
perror("error pthread_mutexattr_destroy.\n");
exit(EXIT_FAILURE);
} n_loops = (argc > ) ? atoi(argv[]) : ; args[] = (void *)n_loops;
args[] = (void *)&mtx;
s = pthread_create(&t1, , thread_routine, &args);
if (s != )
{
perror("error pthread_create.\n");
exit(EXIT_FAILURE);
} s = pthread_create(&t2, , thread_routine, &args);
if (s != )
{
perror("error pthread_create.\n");
exit(EXIT_FAILURE);
} s = pthread_join(t1, );
if (s != )
{
perror("error pthread_join.\n");
exit(EXIT_FAILURE);
} s = pthread_join(t2, );
if (s != )
{
perror("error pthread_join.\n");
exit(EXIT_FAILURE);
} s = pthread_mutex_destroy(&mtx);
if (s != )
{
perror("error pthread_mutex_destroy.\n");
exit(EXIT_FAILURE);
} printf("Var g_n is [%d].\n", g_n);
exit(EXIT_SUCCESS);
}

多次运行示例3代码生成的程序会看到与示例2代码的程序同样的结果。

本文展示了Linux/Unix线程间同步技术---互斥量的基本功能和基础使用方法,在后面的文章中将会讨论互斥量的其他内容,如锁定互斥量的另外2个API: pthread_mutex_trylock()和pthread_mutex_timedlock() ,互斥量的性能,互斥量的死锁等。欢迎大家参与讨论。

本文参考了Michael Kerrisk的著作《The Linux Programming Interface》(中文版名为:Linux/Unix系统编程手册)第30章的内容,版权相关的问题请联系作者或者相应的出版社。

Linux/Unix 线程同步技术之互斥量(1)的更多相关文章

  1. Linux的线程同步对象:互斥量Mutex,读写锁,条件变量

        进程是Linux资源分配的对象,Linux会为进程分配虚拟内存(4G)和文件句柄等 资源,是一个静态的概念.线程是CPU调度的对象,是一个动态的概念.一个进程之中至少包含有一个或者多个线程.这 ...

  2. 线程同步方式之互斥量Mutex

    互斥量和临界区非常相似,只有拥有了互斥对象的线程才可以访问共享资源,而互斥对象只有一个,因此可以保证同一时刻有且仅有一个线程可以访问共享资源,达到线程同步的目的. 互斥量相对于临界区更为高级,可以对互 ...

  3. Linux下线程同步的几种方法

    Linux下提供了多种方式来处理线程同步,最常用的是互斥锁.条件变量和信号量. 一.互斥锁(mutex) 锁机制是同一时刻只允许一个线程执行一个关键部分的代码.  1. 初始化锁 int pthrea ...

  4. [转]一个简单的Linux多线程例子 带你洞悉互斥量 信号量 条件变量编程

    一个简单的Linux多线程例子 带你洞悉互斥量 信号量 条件变量编程 希望此文能给初学多线程编程的朋友带来帮助,也希望牛人多多指出错误. 另外感谢以下链接的作者给予,给我的学习带来了很大帮助 http ...

  5. 【WIN32进阶之路】:线程同步技术纲要

    前面博客讲了互斥量(MUTEX)和关键段(CRITICAL SECTION)的使用,想来总觉不妥,就如盲人摸象一般,窥其一脚而言象,难免以偏概全,追加一篇博客查遗补漏. win32下的线程同步技术分为 ...

  6. iOS开发系列-线程同步技术

    概述 多线程的本质就是CPU轮流随机分配给每条线程时间片资源执行任务,看起来多条线程同时执行任务. 多条线程同时访问同一块资源,比如操作同一个对象.统一变量.同一个文件,就会引发数据错乱和数据安全的问 ...

  7. C#线程同步技术(二) Interlocked 类

    接昨天谈及的线程同步问题,今天介绍一个比较简单的类,Interlocked.它提供了以线程安全的方式递增.递减.交换和读取值的方法. 它的特点是: 1.相对于其他线程同步技术,速度会快很多. 2.只能 ...

  8. C#线程学习笔记六:线程同步--信号量和互斥体

    本笔记摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/23/Mutex_And_Semaphore.html,记录一下学习过程以备后续查用.     ...

  9. linux 线程的同步 一 (互斥量和信号量)

    互斥量(Mutex) 互斥量表现互斥现象的数据结构,也被当作二元信号灯.一个互斥基本上是一个多任务敏感的二元信号,它能用作同步多任务的行为,它常用作保护从中断来的临界段代码并且在共享同步使用的资源. ...

随机推荐

  1. 一个View的子类实例化

    View子类的实例化.如果是在activity中通过findViewById的形式实例化,那么它的具体的构造函数是什么呢,看看父类View的源码就容易发现是 通过这个构造函数实例化的 public V ...

  2. test 2016-12-28

    // dpm(variable_get('node_submitted_page'));// //0// dpm(variable_get('language_count'));// //i3 = i ...

  3. 效率最高的Excel数据导入---(c#调用SSIS Package将数据库数据导入到Excel文件中【附源代码下载】) 转

    效率最高的Excel数据导入---(c#调用SSIS Package将数据库数据导入到Excel文件中[附源代码下载])    本文目录: (一)背景 (二)数据库数据导入到Excel的方法比较   ...

  4. [python] 常用正则表达式爬取网页信息及分析HTML标签总结【转】

    [python] 常用正则表达式爬取网页信息及分析HTML标签总结 转http://blog.csdn.net/Eastmount/article/details/51082253 标签: pytho ...

  5. 【翻译】安卓新播放器EXOplayer介绍

    http://developer.android.com/guide/topics/media/exoplayer.html   前言: Playing videos and music is a p ...

  6. Microsoft Office Project 相关教程 收集

    Project教程 如何建立任务间链接 Project教程:[10]如何将项目插入主项目 如何有效使用Project(1)——编制进度计划.保存基准 如何有效使用Project(2)——进度计划的执行 ...

  7. 为什么.Net要求序列化的类必须有一个无参数的构造函数

    刚才用xml序列化器,序列化一个类,结果报错说序列化的类必须带有一个无参的构造函数,好奇怪啊.为什么要有这么苛刻的条件,而且xml序列化还要求序列化的成员是public. 我以前一直觉得序列化器是一个 ...

  8. $(this)在ajax中无效的解决方案

    在ajax方法里写$(this)指向的是最近调用它的jquery对象,所以这里的$(this)指的是ajax对象,而不是$(".enter_caozuo").find(" ...

  9. WEBSTORM 2016.3 activation code激活

    选择activation code 激活方式,复制粘贴下面的激活码43B4A73YYJ-eyJsaWNlbnNlSWQiOiI0M0I0QTczWVlKIiwibGljZW5zZWVOYW1lIjoi ...

  10. javascript在IE/FF/Chrome的一些兼容问题

    1.获取滚动条高度 var top=document.body.scrollTop||document.documentElement.scrollTop; 2.事件监听 var addEvent = ...