Linux系统编程(28)——线程间同步
多个线程同时访问共享数据时可能会冲突,这跟前面讲信号时所说的可重入性是同样的问题。比如两个线程都要把某个全局变量增加1,这个操作在某平台需要三条指令完成:
从内存读变量值到寄存器
寄存器的值加1
将寄存器的值写回内存
假设两个线程在多处理器平台上同时执行这三条指令,则可能导致下图所示的结果,最后变量只加了一次而非两次
我们通过一个简单的程序观察这一现象。上图所描述的现象从理论上是存在这种可能的,但实际运行程序时很难观察到,为了使现象更容易观察到,我们把上述三条指令做的事情用更多条指令来做:
val= counter;
printf("%x:%d\n", (unsigned int)pthread_self(), val + 1);
counter= val + 1;
我们在“读取变量的值”和“把变量的新值保存回去”这两步操作之间插入一个printf调用,它会执行write系统调用进内核,为内核调度别的线程执行提供了一个很好的时机。我们在一个循环中重复上述操作几千次,就会观察到访问冲突的现象。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h> #define NLOOP 5000 int counter; /* incremented by threads */ void *doit(void *); int main(int argc, char **argv)
{
pthread_ttidA, tidB; pthread_create(&tidA,NULL, &doit, NULL);
pthread_create(&tidB,NULL, &doit, NULL); /* wait for both threads to terminate */
pthread_join(tidA,NULL);
pthread_join(tidB,NULL); return0;
} void *doit(void *vptr)
{
int i, val; /*
* Each thread fetches, prints, and incrementsthe counter NLOOP times.
* The value of the counter should increasemonotonically.
*/ for(i = 0; i < NLOOP; i++) {
val= counter;
printf("%x:%d\n", (unsigned int)pthread_self(), val + 1);
counter= val + 1;
} returnNULL;
}
我们创建两个线程,各自把counter增加5000次,正常情况下最后counter应该等于10000,但事实上每次运行该程序的结果都不一样,有时候数到5000多,有时候数到6000多。
对于多线程的程序,访问冲突的问题是很普遍的,解决的办法是引入互斥锁(Mutex,Mutual Exclusive Lock),获得锁的线程可以完成“读-修改-写”的操作,然后释放锁给其它线程,没有获得锁的线程只能等待而不能访问共享数据,这样“读-修改-写”三步操作组成一个原子操作,要么都执行,要么都不执行,不会执行到中间被打断,也不会在其它处理器上并行做这个操作。
Mutex用pthread_mutex_t类型的变量表示,可以这样初始化和销毁:
#include <pthread.h> int pthread_mutex_destroy(pthread_mutex_t*mutex);
int pthread_mutex_init(pthread_mutex_t*restrict mutex,
const pthread_mutexattr_t *restrict attr);
pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;
返回值:成功返回0,失败返回错误号。
pthread_mutex_init函数对Mutex做初始化,参数attr设定Mutex的属性,如果attr为NULL则表示缺省属性,本章不详细介绍Mutex属性,感兴趣的读者可以参考[APUE2e]。用pthread_mutex_init函数初始化的Mutex可以用pthread_mutex_destroy销毁。如果Mutex变量是静态分配的(全局变量或static变量),也可以用宏定义PTHREAD_MUTEX_INITIALIZER来初始化,相当于用pthread_mutex_init初始化并且attr参数为NULL。Mutex的加锁和解锁操作可以用下列函数:
#include <pthread.h> int pthread_mutex_lock(pthread_mutex_t*mutex);
int pthread_mutex_trylock(pthread_mutex_t*mutex);
int pthread_mutex_unlock(pthread_mutex_t*mutex);
返回值:成功返回0,失败返回错误号。
一个线程可以调用pthread_mutex_lock获得Mutex,如果这时另一个线程已经调用pthread_mutex_lock获得了该Mutex,则当前线程需要挂起等待,直到另一个线程调用pthread_mutex_unlock释放Mutex,当前线程被唤醒,才能获得该Mutex并继续执行。
如果一个线程既想获得锁,又不想挂起等待,可以调用pthread_mutex_trylock,如果Mutex已经被另一个线程获得,这个函数会失败返回EBUSY,而不会使线程挂起等待。
现在我们用Mutex解决先前的问题:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h> #define NLOOP 5000 int counter; /* incremented by threads */
pthread_mutex_t counter_mutex =PTHREAD_MUTEX_INITIALIZER; void *doit(void *); int main(int argc, char **argv)
{
pthread_ttidA, tidB; pthread_create(&tidA,NULL, doit, NULL);
pthread_create(&tidB,NULL, doit, NULL); /* wait for both threads to terminate */
pthread_join(tidA,NULL);
pthread_join(tidB,NULL); return0;
} void *doit(void *vptr)
{
int i, val; /*
* Each thread fetches, prints, and incrementsthe counter NLOOP times.
* The value of the counter should increasemonotonically.
*/ for(i = 0; i < NLOOP; i++) {
pthread_mutex_lock(&counter_mutex); val= counter;
printf("%x:%d\n", (unsigned int)pthread_self(), val + 1);
counter= val + 1; pthread_mutex_unlock(&counter_mutex);
} returnNULL;
}
这样运行结果就正常了,每次运行都能数到10000。
那么挂起等待”和“唤醒等待线程”的操作如何实现?每个Mutex有一个等待队列,一个线程要在Mutex上挂起等待,首先在把自己加入等待队列中,然后置线程状态为睡眠,然后调用调度器函数切换到别的线程。一个线程要唤醒等待队列中的其它线程,只需从等待队列中取出一项,把它的状态从睡眠改为就绪,加入就绪队列,那么下次调度器函数执行时就有可能切换到被唤醒的线程。
一般情况下,如果同一个线程先后两次调用lock,在第二次调用时,由于锁已经被占用,该线程会挂起等待别的线程释放锁,然而锁正是被自己占用着的,该线程又被挂起而没有机会释放锁,因此就永远处于挂起等待状态了,这叫做死锁(Deadlock)。另一种典型的死锁情形是这样:线程A获得了锁1,线程B获得了锁2,这时线程A调用lock试图获得锁2,结果是需要挂起等待线程B释放锁2,而这时线程B也调用lock试图获得锁1,结果是需要挂起等待线程A释放锁1,于是线程A和B都永远处于挂起状态了。不难想象,如果涉及到更多的线程和更多的锁,有没有可能死锁的问题将会变得复杂和难以判断。
写程序时应该尽量避免同时获得多个锁,如果一定有必要这么做,则有一个原则:如果所有线程在需要多个锁时都按相同的先后顺序(常见的是按Mutex变量的地址顺序)获得锁,则不会出现死锁。比如一个程序中用到锁1、锁2、锁3,它们所对应的Mutex变量的地址是锁1<锁2<锁3,那么所有线程在需要同时获得2个或3个锁时都应该按锁1、锁2、锁3的顺序获得。如果要为所有的锁确定一个先后顺序比较困难,则应该尽量使用pthread_mutex_trylock调用代替pthread_mutex_lock调用,以免死锁。
Linux系统编程(28)——线程间同步的更多相关文章
- linux应用编程之进程间同步
一.描述 在操作系统中,异步并发执行环境下的一组进程,因为相互制约关系,进而互相发送消息.互相合作.互相等待,使得各进程按一定的顺序和速度执行,称为进程间的同步.具有同步关系的一组并发进程,称为合作进 ...
- linux系统编程:线程原语
线程原语 线程概念 线程(thread),有时被称为轻量级进程(Lightweight Process,LWP).是程序运行流的最小单元.一个标准的线程由线程ID.当前指令指针(PC),寄存器集合和堆 ...
- Linux系统编程:线程控制
一.提出问题 问1.线程存在的意义是什么?什么时候适合使用多线程? 答1.在单进程环境中实现多任务,线程可访问其所在进程的资源,例如内存.描述符等.对于单进程,如果要完成多项任务,这些任务只能依次执行 ...
- linux系统编程:线程同步-相互排斥量(mutex)
线程同步-相互排斥量(mutex) 线程同步 多个线程同一时候訪问共享数据时可能会冲突,于是须要实现线程同步. 一个线程冲突的演示样例 #include <stdio.h> #includ ...
- linux系统编程:线程同步-信号量(semaphore)
线程同步-信号量(semaphore) 生产者与消费者问题再思考 在实际生活中,仅仅要有商品.消费者就能够消费,这没问题. 但生产者的生产并非无限的.比如,仓库是有限的,原材料是有限的,生产指标受消费 ...
- Linux系统编程(29)——线程间同步(续篇)
线程间的同步还有这样一种情况:线程A需要等某个条件成立才能继续往下执行,现在这个条件不成立,线程A就阻塞等待,而线程B在执行过程中使这个条件成立了,就唤醒线程A继续执行.在pthread库中通过条件变 ...
- Linux系统编程 —线程同步概念
同步概念 同步,指对在一个系统中所发生的事件之间进行协调,在时间上出现一致性与统一化的现象. 但是,对于不同行业,对于同步的理解略有不同.比如:设备同步,是指在两个设备之间规定一个共同的时间参考:数据 ...
- Linux 系统编程 学习:11-线程:线程同步
Linux 系统编程 学习:11-线程:线程同步 背景 上一讲 我们介绍了线程的属性 有关设置.这一讲我们来看线程之间是如何同步的. 额外安装有关的man手册: sudo apt-get instal ...
- linux系统编程--线程同步
同步概念 所谓同步,即同时起步,协调一致.不同的对象,对“同步”的理解方式略有不同. 如,设备同步,是指在两个设备之间规定一个共同的时间参考: 数据库同步,是指让两个或多个数据库内容保持一致,或者按需 ...
- Linux系统编程—进程间同步
我们知道,线程间同步有多种方式,比如:信号量.互斥量.读写锁,等等.那进程间如何实现同步呢?本文介绍两种方式:互斥量和文件锁. 互斥量mutex 我们已经知道了互斥量可以用于在线程间同步,但实际上,互 ...
随机推荐
- Linux系统下查看某文件修改的时间戳
Linux系统下查看某文件修改的时间戳查看文件时间戳命令:stat awk.txt File: `awk.txt' Size: 20 Blocks: 8 I ...
- java 全角字符半角字符转换
/// <summary> /// 判断字符是否英文半角字符或标点 /// </summary> /// <remarks> /// 32 空格 /// 33 ...
- VelocityTracker简单介绍
翻译自:http://developer.android.com/reference/android/view/VelocityTracker.html 參照自: http://blog.jrj.co ...
- [Falcor] Intro to JSON Graph
JSON is a very commonly used data interchange format. Unfortunately while most application domain mo ...
- StarUML中时序图添加小人
转载于 http://blog.csdn.net/longyuhome/article/details/9011629 在看时序图的例子的时候,发现有些的时序图上有小人的图标,可是一些UML工具却没有 ...
- Java设计模式---组合模式
一.组合模式定义 组合模式定义: Compose objects into tree structures to represent part-whole hierarchies. Composite ...
- WIN7 64位上 MYSQL的ODBC一些个人理解
最近电脑中毒了,无奈之下把32位的xp换成了64位的win7,无聊耗时的搭完开发环境,装了vs6.0(忽略掉一切的不兼容),装完64位的mysql(“mysql-5.6.10-winx64.zip”) ...
- python面对对象编程------4:类基本的特殊方法__str__,__repr__,__hash__,__new__,__bool__,6大比较方法
一:string相关:__str__(),__repr__(),__format__() str方法更面向人类阅读,print()使用的就是str repr方法更面对python,目标是希望生成一个放 ...
- EF中使用Contains方法
第一种情况 var db=new ECEntities(); var list=new []{"8","9"}; var result=from a in db ...
- 关于在head里的link href=<%=%>,其中前置百分号给编码了的解决方案
做了一个项目,主要是能够自动换模板,实际就是插入数据库那个css名称,然后前台取出那个值,放入getcss变量里(getcss自己定义的一个变量),然后通过link href=<%=%>取 ...