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) 互斥量表现互斥现象的数据结构,也被当作二元信号灯.一个互斥基本上是一个多任务敏感的二元信号,它能用作同步多任务的行为,它常用作保护从中断来的临界段代码并且在共享同步使用的资源. ...
随机推荐
- 一个View的子类实例化
View子类的实例化.如果是在activity中通过findViewById的形式实例化,那么它的具体的构造函数是什么呢,看看父类View的源码就容易发现是 通过这个构造函数实例化的 public V ...
- test 2016-12-28
// dpm(variable_get('node_submitted_page'));// //0// dpm(variable_get('language_count'));// //i3 = i ...
- 效率最高的Excel数据导入---(c#调用SSIS Package将数据库数据导入到Excel文件中【附源代码下载】) 转
效率最高的Excel数据导入---(c#调用SSIS Package将数据库数据导入到Excel文件中[附源代码下载]) 本文目录: (一)背景 (二)数据库数据导入到Excel的方法比较 ...
- [python] 常用正则表达式爬取网页信息及分析HTML标签总结【转】
[python] 常用正则表达式爬取网页信息及分析HTML标签总结 转http://blog.csdn.net/Eastmount/article/details/51082253 标签: pytho ...
- 【翻译】安卓新播放器EXOplayer介绍
http://developer.android.com/guide/topics/media/exoplayer.html 前言: Playing videos and music is a p ...
- Microsoft Office Project 相关教程 收集
Project教程 如何建立任务间链接 Project教程:[10]如何将项目插入主项目 如何有效使用Project(1)——编制进度计划.保存基准 如何有效使用Project(2)——进度计划的执行 ...
- 为什么.Net要求序列化的类必须有一个无参数的构造函数
刚才用xml序列化器,序列化一个类,结果报错说序列化的类必须带有一个无参的构造函数,好奇怪啊.为什么要有这么苛刻的条件,而且xml序列化还要求序列化的成员是public. 我以前一直觉得序列化器是一个 ...
- $(this)在ajax中无效的解决方案
在ajax方法里写$(this)指向的是最近调用它的jquery对象,所以这里的$(this)指的是ajax对象,而不是$(".enter_caozuo").find(" ...
- WEBSTORM 2016.3 activation code激活
选择activation code 激活方式,复制粘贴下面的激活码43B4A73YYJ-eyJsaWNlbnNlSWQiOiI0M0I0QTczWVlKIiwibGljZW5zZWVOYW1lIjoi ...
- javascript在IE/FF/Chrome的一些兼容问题
1.获取滚动条高度 var top=document.body.scrollTop||document.documentElement.scrollTop; 2.事件监听 var addEvent = ...