Linux/Unix 线程同步技术之互斥量(1)
众所周知,互斥量(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)的更多相关文章
- Linux的线程同步对象:互斥量Mutex,读写锁,条件变量
进程是Linux资源分配的对象,Linux会为进程分配虚拟内存(4G)和文件句柄等 资源,是一个静态的概念.线程是CPU调度的对象,是一个动态的概念.一个进程之中至少包含有一个或者多个线程.这 ...
- 线程同步方式之互斥量Mutex
互斥量和临界区非常相似,只有拥有了互斥对象的线程才可以访问共享资源,而互斥对象只有一个,因此可以保证同一时刻有且仅有一个线程可以访问共享资源,达到线程同步的目的. 互斥量相对于临界区更为高级,可以对互 ...
- Linux下线程同步的几种方法
Linux下提供了多种方式来处理线程同步,最常用的是互斥锁.条件变量和信号量. 一.互斥锁(mutex) 锁机制是同一时刻只允许一个线程执行一个关键部分的代码. 1. 初始化锁 int pthrea ...
- [转]一个简单的Linux多线程例子 带你洞悉互斥量 信号量 条件变量编程
一个简单的Linux多线程例子 带你洞悉互斥量 信号量 条件变量编程 希望此文能给初学多线程编程的朋友带来帮助,也希望牛人多多指出错误. 另外感谢以下链接的作者给予,给我的学习带来了很大帮助 http ...
- 【WIN32进阶之路】:线程同步技术纲要
前面博客讲了互斥量(MUTEX)和关键段(CRITICAL SECTION)的使用,想来总觉不妥,就如盲人摸象一般,窥其一脚而言象,难免以偏概全,追加一篇博客查遗补漏. win32下的线程同步技术分为 ...
- iOS开发系列-线程同步技术
概述 多线程的本质就是CPU轮流随机分配给每条线程时间片资源执行任务,看起来多条线程同时执行任务. 多条线程同时访问同一块资源,比如操作同一个对象.统一变量.同一个文件,就会引发数据错乱和数据安全的问 ...
- C#线程同步技术(二) Interlocked 类
接昨天谈及的线程同步问题,今天介绍一个比较简单的类,Interlocked.它提供了以线程安全的方式递增.递减.交换和读取值的方法. 它的特点是: 1.相对于其他线程同步技术,速度会快很多. 2.只能 ...
- C#线程学习笔记六:线程同步--信号量和互斥体
本笔记摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/23/Mutex_And_Semaphore.html,记录一下学习过程以备后续查用. ...
- linux 线程的同步 一 (互斥量和信号量)
互斥量(Mutex) 互斥量表现互斥现象的数据结构,也被当作二元信号灯.一个互斥基本上是一个多任务敏感的二元信号,它能用作同步多任务的行为,它常用作保护从中断来的临界段代码并且在共享同步使用的资源. ...
随机推荐
- USB传输协议。——Arvin
问题一:USB的传输线结构是如何的呢? 答案一:一条USB的传输线分别由地线.电源线.D+.D-四条线构成,D+和D-是差分输入线,它使用的是3.3V的电压(注意哦,与CMOS的5V电平不同),而电源 ...
- SQL表关联赋值、系统表、表数据删除
1. 表与表的关联赋值(用于表与表之间有关联字段,数据互传) 双表关联赋值 UPDATE #B SET #B.D=#A.B from #B inner join #A on #B.C=#A.A 多表关 ...
- excel读取
一.jar包 二.工具类 package excel; import java.io.FileInputStream; import java.io.FileNotFoundException; im ...
- TableView与delegate、dataResource
小梦这几天学习tableView是深有体会了 废话不多说,来一波 首先,创建一个测试项目 如图 创建好,在项目结构中另外弄一个GroupFile,创建storyBoard和CocoaTouch 在st ...
- vue切换按钮(关闭消失型)
弹窗: <div class="pop" v-show="isShow"> <i class="iconfont icon-icon ...
- css学习笔记 6
对于css样式文件的规划方式将直接影响后期对web站点维护的效率,对于大型的复杂站点一般将css样式文件分为两部分:页面的全局定义及处理页面基本布局的css样式文件和处理细节方面的css样式文件.这样 ...
- Linux中Screen命令使用方法
一.使用Screen创建一个Session screen -S sessionName 注:sessionName是要删除的session名字 二.结束一个Screen创建的session 1.首先使 ...
- web前端代码编写体验
最好是使用HTML5的有意义的标签,并尝试在不同的,老版本的浏览器中也保持一致,不然你会发现,满屏都是div,后期进行代码验证,修改和查找简直是一场大灾难. 而且,编写代码的时候,为了方便以后代码的复 ...
- Spring Bean
一.Spring的几大模块:Data access & Integration.Transcation.Instrumentation.Core Spring Container.Testin ...
- sass1:
less在处理CSS动画时,非常恶心,决定转向sass了.sass诞生得比less早,只是因为它是ruby写的,因此受众面够少.但我们不需要自己下编译器或使用命令行,我们可以koala这神器 首先几个 ...