当前位置: 首页 > 编程日记 > 正文

Linux多线程与同步

作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明。谢谢!

 

典型的UNIX系统都支持一个进程创建多个线程(thread)。在Linux进程基础中提到,Linux以进程为单位组织操作,Linux中的线程也都基于进程。尽管实现方式有异于其它的UNIX系统,但Linux的多线程在逻辑和使用上与真正的多线程并没有差别。

 

1. 多线程

我们先来看一下什么是多线程。在Linux从程序到进程中,我们看到了一个程序在内存中的表示。这个程序的整个运行过程中,只有一个控制权的存在。当函数被调用的时候,该函数获得控制权,成为激活(active)函数,然后运行该函数中的指令。与此同时,其它的函数处于离场状态,并不运行。如下图所示:

Linux从程序到进程

 

我们看到,各个方块之间由箭头连接。各个函数就像是连在一根线上一样,计算机像一条流水线一样执行各个函数中定义的操作。这样的一个程序叫做单线程程序。


多线程就是允许一个进程内存在多个控制权,以便让多个函数同时处于激活状态,从而让多个函数的操作同时运行。即使是单CPU的计算机,也可以通过不停地在不同线程的指令间切换,从而造成多线程同时运行的效果。如下图所示,就是一个多线程的流程:

main()到func3()再到main()构成一个线程,此外func1()和func2()构成另外两个线程。操作系统一般都有一些系统调用来让你将一个函数运行成为一个新的线程。

 

回忆我们在Linux从程序到进程中提到的stack的功能和用途。由于stack中只有最下方的stack frame可以被读取,所以我们只能有该frame对应的单一函数处于激活状态。为了实现多线程,我们必须绕开stack带给我们的限制。为此,当我们创建一个新的线程的时候,我们为这个线程创建一个新的stack。当该stack执行到全部弹出的时候,该线程完成任务并结束。所以,多线程的进程在内存中会有多个stack,相互之间以一定的空白区域隔开,以备stack的增长。每个线程随时可以使用自己stack中最下方的stack frame中的参数和变量,并与其它线程共享内存中的Text,heap和global data区域。在上面的例子中,我们将在进程空间中有三个stack。

(要注意的是,对于多线程来说,由于同一个进程空间中存在多个stack,任何一个空白区域被填满都会导致stack overflow的问题。)

 

2. 并发

多线程相当于一个并发(concunrrency)系统。并发系统一般同时执行多个任务。如果多个任务可以共享资源,特别是同时写入某个变量的时候,就需要解决同步的问题。比如说,我们有一个多线程火车售票系统,用全局变量i存储剩余的票数。多个线程不断地卖票(i = i - 1),直到剩余票数为0。所以每个都需要执行如下操作:

复制代码
/*mu is a global mutex*/

while
(1) {    /*infinite loop*/if (i != 0) i = i -1else {printf("no more tickets");exit();} }
复制代码

如果只有一个线程执行上面的程序的时候(相当于一个窗口售票),则没有问题。但如果多个线程都执行上面的程序(相当于多个窗口售票), 我们就会出现问题。我们会看到,其根本原因在于同时发生的各个线程都可以对i读取和写入。

我们这里的if结构会给CPU两个指令, 一个是判断是否有剩余的票(i != 0), 一个是卖票 (i = i -1)。某个线程会先判断是否有票(比如说此时i为1),但两个指令之间存在一个时间窗口,其它线程可能在此时间窗口内执行卖票操作(i = i -1),导致该线程卖票的条件不再成立。但该线程由于已经执行过了判断指令,所以无从知道i发生了变化,所以继续执行卖票指令,以至于卖出不存在的票 (i成为负数)。对于一个真实的售票系统来说,这将成为一个严重的错误 (售出了过多的票,火车爆满)。

在并发情况下,指令执行的先后顺序由内核决定。同一个线程内部,指令按照先后顺序执行,但不同线程之间的指令很难说清除哪一个会先执行。如果运行的结果依赖于不同线程执行的先后的话,那么就会造成竞争条件(race condition),在这样的状况下,计算机的结果很难预知。我们应该尽量避免竞争条件的形成。最常见的解决竞争条件的方法是将原先分离的两个指令构成不可分隔的一个原子操作(atomic operation),而其它任务不能插入到原子操作中。

 

3. 多线程同步(synchronization)

对于多线程程序来说,同步是指在一定的时间内只允许某一个线程访问某个资源 。而在此时间内,不允许其它的线程访问该资源。我们可以通过互斥锁(mutex)条件变量(condition variable)读写锁(reader-writer lock)来同步资源。

 

1) mutex

mutex是一个特殊的变量,它有锁上(lock)和打开(unlock)两个状态。mutex一般被设置成全局变量。打开的mutex可以由某个线程获得。一旦获得,这个mutex会锁上,此后只有该线程有权打开。其它想要获得mutex的线程,会等待直到mutex再次打开的时候。我们可以将mutex想像成为一个只能容纳一个人的洗手间,当某个人进入洗手间的时候,可以从里面将洗手间锁上。其它人只能在mutex外面等待那个人出来,才能进去。在外面等候的人并没有排队,谁先看到洗手间空了,就可以首先冲进去。

上面的问题很容易使用mutex的问题解决,每个线程的程序可以改为:

复制代码
/*mu is a global mutex*/

while (1) { /*infinite loop*/mutex_lock(mu);       /*aquire mutex and lock it, if cannot, wait until mutex is unblocked*/if (i != 0) i = i - 1;else {printf("no more tickets");exit();}mutex_unlock(mu);     /*release mutex, make it unblocked*/ }
复制代码

第一个执行mutex_lock()的线程会先获得mu。其它想要获得mu的线程必须等待,直到第一个线程执行到mutex_unlock()释放mu,才可以获得mu,并继续执行线程。所以线程在mutex_lock()和mutex_unlock()之间的操作时,不会被其它线程影响,就构成了一个原子操作

需要注意的时候,如果存在某个线程依然使用原先的程序 (即不尝试获得mu,而直接修改i),mutex不能阻止该程序修改i,mutex就失去了保护资源的意义。所以,mutex机制需要程序员自己来写出完善的程序来实现mutex的功能。我们下面讲的其它机制也是如此。

 

2) condition variable

condition variable是另一种常用的变量。它也常常被保存为全局变量,并和mutex合作。

 

假设我们有这样一个状况: 我们有100个工人,每人负责装修一个房间。当有10个房间装修完成的时候,我们就通知相应的十个工人一起去喝啤酒。我们如何实现呢?我们可以让工人在装修好房间之后,去检查已经装修好的房间数。但多线程条件下,会有竞争条件的危险(其他工人会在该工人装修好房子和检查之间完成工作)。

复制代码
/*mu: global mutex, cond: global codition variable, num: global int*/
mutex_lock(mu)num
= num + 1; /*worker build the room*/if (num <= 10) { /*worker is within the first 10 to finish*/cond_wait(mu, cond);     /*wait*/printf("drink beer"); } else if (num = 11) { /*workder is the 11th to finish*/cond_broadcast(mu, cond);        /*inform the other 9 to wake up*/ }mutex_unlock(mu);
复制代码

通常, condition variable除了要和mutex配合之外,还需要和另一个全局变量配合(这里的num, 也就是装修好的房间数)。这个全局变量用来构成各个条件。

我们让工人在装修好房间(num = num + 1)之后,去检查已经装修好的房间数( num < 10 )。由于mu被锁上,所以不会有其他工人在此期间装修房间(改变num的值)。如果该工人是前十个完成的人,那么我们就调用cond_wait()函数。
cond_wait()做两件事情,一个是释放mu,从而让别的工人可以建房。另一个是等待,直到cond的通知。这样的话,符合条件的线程就开始等待。当有通知(第十个房间已经修建好)到达的时候,condwait()会再次锁上mu,并恢复线程的运行,我们会执行下一句prinft("drink beer") (我们以此来代表喝啤酒)。此后直到mutex_unlock()就构成了另一个mutex结构。

那么如何让前面十个调用cond_wait()的线程得到通知呢?我们注意到if还有另一种可能,也就是修建好第11个房间的人负责调用cond_broadcast()。它会给所有调用cond_wait()的线程放送通知,以便让那些线程恢复运行。

 

Condition variable特别适用于多个线程等待某个条件的发生。如果不使用Condition variable,那么每个线程就需要不断尝试获得mutex并检查条件是否发生,这样大大浪费了系统的资源。

 

3) reader-writer lock

Reader-writer lock与mutex非常相似。r、RW lock有三种状态: 共享读取锁(shared-read)互斥写入锁(exclusive-write lock), 打开(unlock)。后两种状态与之前的mutex两种状态完全相同。

一个unlock的RW lock可以被某个线程获取R锁或者W锁。

如果被一个线程获得R锁,RW lock可以被其它线程继续获得R锁,而不必等待该线程释放R锁。但是,如果此时有其它线程想要获得W锁,它必须等到所有持有共享读取锁的线程释放掉各自的R锁。

如果一个锁被一个线程获得W锁,那么其它线程,无论是想要获取R锁还是W锁,都必须等待该线程释放W锁。

这样,多个线程就可以同时读取共享资源。而具有危险性的写入操作则得到了互斥锁的保护。

 

我们需要同步并发系统,这为程序员编程带来了难度。但是多线程系统可以很好的解决许多IO瓶颈的问题。比如我们监听网络端口。如果我们只有一个线程,那么我们必须监听,接收请求,处理,回复,再监听。如果我们使用多线程系统,则可以让多个线程监听。当我们的某个线程进行处理的时候,我们还可以有其他的线程继续监听,这样,就大大提高了系统的利用率。在数据越来越大,服务器读写操作越来越多的今天,这具有相当的意义。多线程还可以更有效地利用多CPU的环境。

(就像做饭一样,不断切换去处理不同的菜。)

 

本文中所使用的程序采用伪C的写法。不同的语言有不同的函数名(比如mutex_lock)。这里关注的是逻辑上的概念,而不是具体的实现和语言规范。

 

总结:

multiple threads, multiple stacks

race condition

mutex, condition variable, RW lock

相关文章:

Java项目:校园外卖点餐系统(java+SSM+JSP+maven+mysql)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 一、项目简述 环境配置&#xff1a; Jdk1.8 Tomcat8.5 mysql Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09; 项目技术&#xff1a; JSP Spring SpringMVC MyBatis cs…

基于css3 transform实现散乱的照片排列

分享一款基于css3 transform实现散乱的照片排列。这是一款简单零散的css3相册排列特效下载。效果图如下&#xff1a; 在线预览 源码下载 实现的代码。 html代码&#xff1a; <div class"main"><div class"pic pic1"><img src"1.jpg…

C++利用二级指针做函数形参来进行修改实参的实例分析

在学C/C的时候&#xff0c;我们都会了解到一级指针&#xff0c;int* i NULL; 和二级指针int ** pp NULL; 但是具体的一些应用我们可能很难理解&#xff0c;如果我们要取int*的地址&#xff0c;我们就需要int**&#xff0c;这是因为指针传递本质上还是值传递,本质很难理解&a…

SpringBoot 优雅实现超大文件上传,通用方案

通俗的说,你把要上传的东西上传,服务器会先做MD5校验,如果服务器上有一样的东西,它就直接给你个新地址,其实你下载的都是服务器上的同一个文件,想要不秒传,其实只要让MD5改变,就是对文件本身做一下修改(改名字不行),例如一个文本文件,你多加几个字,MD5就变了,就不会秒传了。分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(我们称之为Part)来进行分别上传,上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件。

robotframework - 运行报错提示 No keyword with name 'Open Browser' found.

用下面的例子为例&#xff1a; 1、输入以上robot脚本提示&#xff1a; 2、经查阅资料&#xff0c;大部分都使用的是selenium2 版本&#xff0c;无法解该的问题&#xff0c;目前小编使用的是selenium3&#xff0c;不知道selenium是哪个版本的话&#xff0c;用pip show selenium…

Linux从程序到进程

作者&#xff1a;Vamei 出处&#xff1a;http://www.cnblogs.com/vamei 欢迎转载&#xff0c;也请保留这段声明。谢谢&#xff01; 计算机如何执行进程呢&#xff1f;这可以说是计算机运行的核心问题。即使我们已经编写好程序&#xff0c;但程序是死的文本&#xff0c;只有成为…

struct stat结构体的详解和用法

[cpp] view plaincopy//! 需要包含de头文件 #include <sys/types.h> #include <sys/stat.h> S_ISLNK(st_mode)&#xff1a;是否是一个连接.S_ISREG(st_mode)&#xff1a;是否是一个常规文件.S_ISDIR(st_mode)&#xff1a;是否是一个目录S_ISCHR(st_mode)&a…

反射setaccessible_反射技术

反射机制作用动态的加载类、动态的获取类的信息(属性&#xff0c;方法&#xff0c;构造器)动态构造对象动态调用类和对象的任意方法、构造器动态调用和处理属性获取泛型信息处理注解获取Class对象的方式有哪些&#xff1f;1. Class clazz Class.forName(全限定类名);2. 类名.c…

pthread_cond_wait()函数的详解

http://hi.baidu.com/tjuer/item/253cc6d66b921317d90e4483 了解 pthread_cond_wait() 的作用非常重要 -- 它是 POSIX 线程信号发送系统的核心&#xff0c;也是最难以理解的部分。 首先&#xff0c;让我们考虑以下情况&#xff1a;线程为查看已链接列表而锁定了互斥对象&#x…

CentOS7下python虚拟环境

搭建python虚拟环境 1.我们先创建一个隐藏目录 .virtualenvs&#xff0c;所有的虚拟环境都放在此目录下 &#xff1a;mkdir /root/.virtualenvs 2.安装虚拟环境 确认pip&#xff1a;whereis pip3 pip3 install virtualenv 安装virtualenvwrapper&#xff0c;为避免超时错误&…

WCDMA中的URA和LA/RA

1、关于URA的概念&#xff1a;URA&#xff08;UTRAN Registration Area&#xff09;是UTRAN内部区域的划分适用于UE处于RRC连接状态的情形&#xff0c;而且只能在UTRAN端使用&#xff08;比如由UTRAN发起的寻呼&#xff09;。一 个URA包含了一个或多个Cell&#xff0c;具体由决…

背景音乐的实现

通过利用Service来实现该功能将要播放的歌曲放入raw文件夹中[html] view plaincopy <strong>新建一个AudioService 类&#xff0c;<span style"font-family: Arial, Helvetica, sans-serif;">AudioService 类</span><span style"font-fami…

打牌软件可以控制吗_使用crm软件真的可以帮助企业省钱吗

使用crm软件真的可以帮助企业省钱吗大多数企业管理者认为&#xff1a;“客户关系系统有什么用?真的可以帮助企业发展吗?自己做一套excel版本不就行了”其实&#xff0c;不以为然&#xff0c;当我们去寻找用户时或者管理用户需要工作人员做一些繁琐的事情会极大的降低员工的工…

MS_SQL_获取字符串最后出现的字符串及位置

一&#xff0e;如&#xff1a;6.7.8.2.3.4.x得到最后一个.后面的字符串&#xff1a; declare str1 varchar(50) set str16.7.8.2.3.4.x select REVERSE(SUBSTRING(REVERSE(str1),1,CHARINDEX(.,REVERSE(str1))-1)) -------- string:x-- --------------------------------------…

redis(3)-redis基本类型

在redis安装目录下存在redis自带的客户端&#xff0c;启动即可使用。如果设置了密码&#xff0c;需要输入auth 123456进行验证。123456为密码。 redis的基本数据类型&#xff1a; 1.字符串类型(String)    redis 127.0.0.1:6379> SET mykey "redis" OK redis 1…

虚函数与虚继承寻踪

封装、继承、多态是面向对象语言的三大特性&#xff0c;熟悉C的人对此应该不会有太多异议。C语言提供的struct&#xff0c;顶多算得上对数据的简单封装&#xff0c;而C的引入把struct“升级”为class&#xff0c;使得面向对象的概念更加强大。继承机制解决了对象复用的问题&…

信息记录拉取失败_天猫入驻为什么失败?猫店侠做详细解读

天猫入驻为什么失败&#xff1f;这是很多商家都想要知道的一件事情&#xff0c;猫店侠想说其实这也很正常&#xff0c;只要不是一味盲目的入驻&#xff0c;就还有机会。首先失败商家要看看失败的反馈内容&#xff0c;看看是哪方面不达标&#xff0c;再着重进行补充&#xff0c;…

Linux查看目录挂载点

用命令 df 即可 # df /var/lib/ Filesystem 1K-blocks Used Available Use% Mounted on /dev/sda3 135979984 66905292 62055896 52% /加上-kh更容易看些&#xff1a; # df /var/lib/ -kh Filesystem Size Used Avail Use% Mounted o…

Android:你好,androidX!再见,android.support

190325 补充&#xff1a;莫名问题的解决 181106 补充&#xff1a;修改未迁移成功的三方库 1、AndroidX简介 点击查看Android文档中对androidx的简介 按照官方文档说明 androidx 是对 android.support.xxx 包的整理后产物。由于之前的support包过于混乱&#xff0c;所以&#xf…

设计模式:简单工厂、工厂方法、抽象工厂之小结与区别

简单工厂&#xff0c;工厂方法&#xff0c;抽象工厂都属于设计模式中的创建型模式。其主要功能都是帮助我们把对象的实例化部分抽取了出来&#xff0c;优化了系统的架构&#xff0c;并且增强了系统的扩展性。 本文是本人对这三种模式学习后的一个小结以及对他们之间的区别的理解…

云计算和大数据时代网络技术揭秘(八)数据中心存储FCoE

数据中心存储演化——FCoE数据中心三大基础&#xff1a;主机 网络 存储在云计算推动下&#xff0c;存储基础架构在发生演变传统存储结构DAS、SAN在发展中遇到了布线复杂、能耗增多的缺点&#xff08;原生性&#xff09;&#xff0c;需要对架构做根本的改变。FCoE是业界无可争议…

在plsql里面怎么去掉空行_盐渍樱花怎么做?详细做法告诉您,一年都不会坏,学会再也不用买...

盐渍樱花怎么做&#xff1f;详细做法告诉您&#xff0c;一年都不会坏&#xff0c;赶紧收藏学会它&#xff01;樱花季说的就是现在&#xff0c;虽然到了飘落的季节&#xff0c;但是还是到处可见的樱花朵朵。俗话说&#xff1a;花无百日红。真的是啊&#xff0c;每年的三四月是最…

Linux基础知识——常用shell命令介绍(三)

一、改变文件权限 chmod:change mode 语法&#xff1a;# chmod [选项-option] 权限 FILE 选项&#xff1a;-R 递归修改权限 --reference 参照文件或目录给予权限 权限定义方式&#xff1a; 1.同时修改三类用户的权限: 8进制数字方式 # chmod 666 /abc /*将/abc的权限改为…

免费版CloudFlare CDN基本设置参考

CDN有很多&#xff0c;网上都有介绍&#xff0c;用户比较多的CloudFlare CDN大家都知道&#xff0c;配置起来也比较简单&#xff0c;合理的配置&#xff0c;才能提升网站的速度和网站安全。不同的网站需求配置不一样&#xff0c;以下是我的配置情况&#xff0c;仅供参考。 我网…

从Java类库看设计模式

//From http://www.uml.org.cn/j2ee/201010214.asp 很多时候&#xff0c;对于一个设计来说(软件上的&#xff0c;建筑上的&#xff0c;或者它他工业上的)&#xff0c;经验是至关重要的。好的经验给我们以指导&#xff0c;并节约我们的时间;坏的经验则给我们以借鉴&#xff0c;可…

method=post 怎么让查看源代码看不到_网站文档不能复制怎么办?教你3个小妙招,1分钟轻松化解...

不知道大家平常在查找资料时&#xff0c;碰到网页资料不能下载时&#xff0c;是怎么样进行处理的。那么笔者今天就来分享我查找不能复制文档时&#xff0c;所用的3个小妙招&#xff0c;帮助轻松化解&#xff0c;一起来看看吧。1、保存网页当我们遇到一个不能直接复制的文档&…

Missing number

题目链接&#xff1a;http://acm.hust.edu.cn/vjudge/problem/visitOriginUrl.action?id114468 题目大意&#xff1a; 多组案例T&#xff0c;每个案例含n2个数据&#xff0c;这n2个数据构成一组有序列&#xff0c;现在已知这组数据中的n个&#xff0c;请找出缺失的两个数据。 …

[leetcode]Surrounded Regions @ Python

原题地址&#xff1a;https://oj.leetcode.com/problems/surrounded-regions/ 题意&#xff1a; Given a 2D board containing X and O, capture all regions surrounded by X. A region is captured by flipping all Os into Xs in that surrounded region. For example, X X …

Android Drawable 详解(教你画画!)

参考 1、Android中的Drawable基础与自定义Drawable2、android中的drawable资源3、Android开发之Shape详细解读 Drawable分类 Noxml标签Class类含义1shapeShapeDrawable特定形状&#xff0c;模型的图样2selectorStateListDrawable不同状态选择不同的图样3layer-listLayerDrawabl…

Carrier frequency 和 EARFCN的关系

Carrier frequency 和 EARFCN的关系 我们处理UE log时&#xff0c;看到LTE cell 都是用EARFCN/PCI来标示的&#xff0c;那么EARFCN和frequency 之间是什么关系呢&#xff1f; 1. EARFCN: 缩写: E-UTRA Absolute Radio Frequency Channel Number, 取值范围: 0…