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) 互斥量表现互斥现象的数据结构,也被当作二元信号灯.一个互斥基本上是一个多任务敏感的二元信号,它能用作同步多任务的行为,它常用作保护从中断来的临界段代码并且在共享同步使用的资源. ...
随机推荐
- windbg运行
运行起来会提示windbg is running. BUSY 这个是正常运行的状态,只有发生异常,或者被指定断点,才会中断.
- centos 更新linux内核
之前一直使用centos 7系统进行开发,centos 7.0 系统自带的linux内核版本为 linx 3.10,最近开发需要使用到socket的reuse port特性,该特性在linux 3.9 ...
- linux建立一个快捷方式,连接到另一个目录
sudo ln -s 源目录 目标快捷方式比如你要在/etc下面建立一个叫LXBC553的快捷方式,指向/home/LXBC,那就是sudo ln -s /home/LXBC /etc/LXBC553
- express+gulp构建项目(四)env环境变量
这里的文件的作用是负责设置env环境变量和日志. index.js try { require('dotenv').load({silent: true}); //dotenv从一个.env文件中读取 ...
- JAVA GUI布局管理器
边界布局管理器: a.布局方式:是把整个容器划分为五个部分.东西南北中,南北要贯通,中间最大 (不仅是中间的范围最大,权利也最大)当周边不存在的时候中间会占领周边,当中间不存在的时候周边不能占据中间 ...
- C#编码规范 转 http://www.cnblogs.com/wulinfeng/archive/2012/08/31/2664720.html
C#编码规范 1 规范目的 ……………………………………………………… 3 2 适用范围 ……………………………………………………… 3 3 代码注释 ………………………………………………… ...
- Python’s SQLAlchemy vs Other ORMs[转发 2]Storm
Storm Storm is a Python ORM that maps objects between one or more databases and Python. It allows de ...
- 建站随手记:about server stack
建站需要,随手记: Server Stack: ----------- 标准的mezzanine的Stack设置 前端:Nginx wsgi:gunicorn cms tool: mezzanine ...
- UEditor手动调节其宽度
其高度一般不考虑,给个初始高度,然后任其自动扩展就行,对于其宽度,有两种思路,一种是调节其所在的DIV的宽度,让其自动填充,另一种是直接调节编辑器的宽度: adjust_editor_size: fu ...
- NetworkComms V3 模拟登陆
演示NetworkComms V3的用法 例子很简单 界面如下: 服务器端代码: 开始监听: //服务器开始监听客户端的请求 Connection.StartListening(ConnectionT ...