Youmai の Blog


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

CGI和WSGI

发表于 2015-04-27 | 分类于 python

我对 CGI 的理解是利用程序的标准输入输出流,完成 HTTP 通信。HTTP 是文本协议,每次请求的文本以标准输入流的形式进入服务器端 CGI 程序,创建进程;然后进程的标准输出流作为响应 。

WSGI 和 CGI 原理极其相似,但是是完全不同的实现。WSGI 是 Python 专用的协议,也是输入&输出的方式传输文本流,但不是创建进程,而是对一个 WSGI 程序(callable 的对象,可以是函数也可以是实现了 call 的对象),将 request 作为参数传入(不再是纯文本,而是经过包装),同样将经过包装的 response 作为响应返回。request/response 的包装由 Python 标准库提供。

二者都是标准,都有诸多实现。CGI 最为广泛,无论是二进制程序、perl 脚本、python 脚本还是 PHP 都可以以这样的原理提供 HTTP 服务;WSGI 在 Python 界也是公共标准,主流 Web 框架基本都有实现,例如 Bottle 的 bottle.Bottle (Application 对象)就实现了 WSGI 协议(见 call 方法)。

Django的QuerySets

发表于 2015-04-26 | 分类于 Django

对象关系映射 (ORM) 使得与SQL数据库交互更为简单,不过也被认为效率不高,比原始的SQL要慢。
要有效的使用ORM,意味着需要多少要明白它是如何查询数据库的。本文我将重点介绍如何有效使用 Django ORM系统访问中到大型的数据集。

##Django的queryset是惰性的

Django的queryset对应于数据库的若干记录(row),通过可选的查询来过滤。例如,下面的代码会得到数据库中名字为‘Dave’的所有的人:
?

person_set = Person.objects.filter(first_name="Dave")

上面的代码并没有运行任何的数据库查询。你可以使用person_set,给它加上一些过滤条件,或者将它传给某个函数,这些操作都不会发送给数据库。这是对的,因为数据库查询是显著影响web应用性能的因素之一。
要真正从数据库获得数据,你需要遍历queryset:
?

for person in person_set:
    print(person.last_name)

##Django的queryset是具有cache的

当你遍历queryset时,所有匹配的记录会从数据库获取,然后转换成Django的model。这被称为执行(evaluation)。这些model会保存在queryset内置的cache中,这样如果你再次遍历这个queryset,你不需要重复运行通用的查询。
例如,下面的代码只会执行一次数据库查询:

pet_set = Pet.objects.filter(species="Dog")
# The query is executed and cached.
for pet in pet_set:
    print(pet.first_name)
# The cache is used for subsequent iteration.
for pet in pet_set:
    print(pet.last_name)

##if语句会触发queryset的执行

queryset的cache最有用的地方是可以有效的测试queryset是否包含数据,只有有数据时才会去遍历:

restaurant_set = Restaurant.objects.filter(cuisine="Indian")
# `if`语句会触发queryset的执行。
if restaurant_set:
    # 遍历时用的是cache中的数据
    for restaurant in restaurant_set:
        print(restaurant.name)

##QuerySet的常用API

###去重
distinct方法的作用和SQL的distinct的作用是一样的,这里就不多说了

Author.objects.distinct()

###序列化为JSON
和前端进行交互的时候,序列化成XML的情况还是比较少的,但是序列化成JSON格式的字符串的情况就比较多了,Django的ORM框架提供了values的方法来把实体序列化成json

Blog.objects.values()

###序列化成数组
把对象序列化成数组元素的情况还真的是用的比较少

Entry.objects.values_list('id', 'headline')

###从主表获取外键表的对象
在主表的实体上使用filter,然后再通过主表的实体获取外键的时候,Django会把主表的实体一并查询出来。但是有时候我们只希望从主表开始取数,但是只取外键表实体的信息,这个时候就可以用select_related方法了。第一个参数是主表的外键字段名称,第二个字段嘛。。。照抄就是了

g = Group.objects.select_related('room', 'subject')

当然,也会有只希望拿主表信息,不想把外键字段取回来的情况,把参数设置为none就好了

without_relations = queryset.select_related(None)

###处理从表对象
对于外键表来说,我们可以采用select_related的方法来进行处理。但是对于从表属性来说,就要用到另外一种方法了。(toppings是多对多的属性)

Pizza.objects.all().prefetch_related('toppings')

同样的,不获取外键对象的话把参数设置为none就好了

non_prefetched = qs.prefetch_related(None)

###ORM和SQL的混合:Extra方法
先看看Extra方法的一些例子

####selec参数
Entry.objects.extra(select={‘is_recent’: “pub_date > ‘2006-01-01’”})

####where参数
Entry.objects.extra(where=[“foo=’a’ OR bar = ‘a’”, “baz = ‘a’”])

####order_by参数
q = q.extra(order_by = [‘-is_recent’])

####paramas参数
Entry.objects.extra(where=[‘headline=%s’], params=[‘Lennon’])
其实从官方给的例子看起来,Extra方法主要就是用于在ORM生成SQL的过程中内嵌SQL语句。例如希望在select 背后加入一句sql,例如select …,(select a from …)这种情况下的时候,用Extra方法就很好解决了

###使用Defer和Only来过滤字段
select_related和prefetch_related方法分别用来过滤实体的外键属性和多对多属性,对于主表的字段属性过滤可以采用defer方法来少查询一些字段

Entry.objects.defer("headline", "body")

defer是排除哪些字段,而only是只查询哪些字段

Person.objects.only("name")

###使用select_for_update来简化更新的过程
一般更新实体的时候,我们需要先把实体先查询出来,然后做出相应的更新,再做一次save操作

entity=Entry.objects.filter(...)[0]
entity.name=xx
entity.save()

这个过程显得稍微有点繁琐,所以可以采用一种select_for_update来进行一些简化

entries = Entry.objects.select_for_update().filter(author=request.user)

###使用get_or_create来简化创建对象的过程
同样的,有时候我们会需要先查询对象在不在数据库里面存储,假如没有的话就创建,有的话就取出

obj, created = Person.objects.get_or_create(first_name='John', last_name='Lennon',
              defaults={'birthday': date(1940, 10, 9)})

不过个人觉得这个方法用到的情况很少。。。。

###批量插入数据
Entry.objects.bulk_create([
Entry(headline=”Django 1.0 Released”),
Entry(headline=”Django 1.1 Announced”),
Entry(headline=”Breaking: Django is awesome”)
])
这玩意真的挺有用,具体的就不细说了

###使用Count方法来进行统计
虽然做统计的话可以先把过滤完的对象查询出来,然后再len(xx)一下,但是这种做法会把数据库里面一堆一堆的暑假都查出来的( ̄◇ ̄;),所以当需要做count操作的时候还是老老实实的用sql的count语句,在Django里面的话做法如下

Entry.objects.filter(headline__contains='Lennon').count()

###判断数据是否存在:exists
rs=some_queryset.filter(pk=entry.pk).exists()

一些pythonic代码的例子

发表于 2015-04-16 | 分类于 python

##百分号的使用:
通常我们都是这样格式化字符串的:

print 'hello world programme by %s' % 'python'  

但是如果格式化的字符串中有很多%s,那么程序的可读性就会依靠于%后面 的变量名起得是否好了。

这个时候有一种用dict来格式化的%,我觉得很有用,尤其是在记log的 时候,作为log的格式,可读性非常高。
代码如下:

#字符串  
value = {'what': 'hello, world', 'language': 'python'}  
print '%(what)s, %(language)s' % value  
#也可以包含int的  
value = {'name': 'jianpx', 'age': 23}  
print '%(name)s 's age is  %(age)i' % value

用两个元素之间有对应关系的list构造一个dict:

运用zip可以非常简单的实现:

names = ['jianpx', 'yue']  
ages = [23, 40]  
m = dict(zip(names,ages))  

zip的使用可以help(zip)或者查看官方文档。

##交换两个值:
在其他语言可能要一个临时变量和三句话:

temp = a
a = b
b = temp

但是在python,一句就ok了,而且不需要临时变量:

a,b = b,a

右边的b,a 其实可以理解成一个tuple。

##数量多的字符串相连用join:
python字符串效率问题之一就是在连接字符串的时候使用‘+’号,例如 s = 's1' + 's2' + 's3' + ...+'sN',总共将N个字符串连接起来,但是使用+号的话,python需要申请N-1次内存空间,然后进行字符串拷贝。原因是字符串对象PyStringObject在python当中是不可变对象,所以每当需要合并两个字符串的时候,就要重新申请一个新的内存空间(大小为两个字符串长度之和)来给这个合并之后的新字符串,然后进行拷贝。所以用+号效率非常低。建议在连接字符串的时候使用字符串本身的方法join(list),这个方法能提高效率,原因是它只是申请了一次内存空间,因为它可以遍历list中的元素计算出总共需要申请的内存空间的大小,一次申请完。所以上面的例子可以写成s = ''.join(['s1','s2',....,'sN'])

例子是:

#以前是这样写的  
fruits = ['apple', 'banana']  
result = ''  
for f in fruits:  
    result += f  

#现在可以这样:  
fruits = ['apple', 'banana']  
result = ''.join(fruits)  

##判断一个key是否在一个dict里面:
以前很经常犯的一个mistake是这样做:

if key in dict_example:  
    do something  

现在要这样写,就不用使用in操作了。

if dict_example.has_key(key):  
    do something  

##去掉list中的重复元素:

old_list = [1,1,1,3,4]  
new_list = list(set(old_list))  

##判断元素是否在列表中
如果对没有重复元素的列表对象,要判断某个元素是否在列表里面的话,当这个列表很大的时候,用set会比list
的性能要好,因为对于list,本身允许重复元素存在,所以它不是用hash实现的,但是set不一样,它不允许重复元素,看了python源代码,从set的实现源码setobject.c 中查找key的函数

static setentry *
set_lookkey(PySetObject *so, PyObject *key, register long hash)    

的接口可以看出它真的使用hash去实现的。

所以对于in操作,set的实现是计算这个元素的hash值然后判断,理论上可以达到O(1)

##读文件操作:
以前是这样写的:

#默认文件存在,不处理Exception的情况  
f = open('filename', 'r')  
while 1:  
    line = f.readline()  
    if not line:  
        break  
    print line  

if f:  
    f.close()  

用with关键字可以这样简写了,

from __future__ import with_statement  
with open('filename','r') as f:  
    for line in f:  
        print line  

具体关于with的可以参考这篇文章

##输出数组的index和值:
以前是要这样写的:

l = [1,3,4]  
for i in xrange(len(l)):  
    print '%d, %d' % (i , l[i])  

现在可以用enumerate函数帮助你简写:

l = [1,3, 4]  
for index, value in enumerate(l):  
    print '%d, %d' % (index, value)  

##关于使用map、filter、reduce的例子网上很多,这里不细说了,它们的使用也是pythonic的examples

##分隔一个字符串,去里面的元素,,但是空白字符串不要:
例如, names = ‘jianpx, yy, mm, , kk’

names = 'jianpx, mm, yy, , kk'  
name_list = names.split(',')  
result = []  
for name in name_list:  
    if name:  
        result.append(name)  

现在是这样写的:

names = 'jianpx, yy, mm, , kk'  
result = [name for name in names.split(',') if name.strip()]  

##模拟c语言中的 a?b:c
在python里面可以这样做:

return_value = True if a == 1 else False  

从而代替了这样的代码:

if a == 1:  
    return_value = True  
else   
    return_value = False  

##用Decorator抽离公用代码或者解耦
例如要对一个函数做cache,对一个操作限制权限,如果需求随时可能变化,就是说有可能不需要做cache或者不需要做权限的时候,你如果把实现放到这些函数体里面,那么这时你必须把这些代码删除,而且要很小心。但是如果你用Decorator去做的话, 只要删除函数头顶上的@那一行就可以了。Django经常用这种方法做权限控制。
熟悉decorator的应该都很容易理解。

##如何将list的元素倒序并且生成到新的list呢?

看到一个用list的slice做到的 :

a = [1,2,3,4]  
c = 'abcdef'  
aa= a[::-1]  
cc = c[::-1]  

如果不用生成新的list,直接调用a.reverse()就得了。但是字符串类型没有reverse的方法.

关于list的slice特性, 其实也许很多人平时只是用list[start:end] 这样的, 这个意思是从start开始,每个元素都放到新的list里面, 直到end。但是其实还可以每隔N个元素才取一次的, 这种情况要3个参数:·list[start:end:step],step就是间隔了。

##a = [i for i in xrange(5)] 和 a = (i for i in xrange(5))

虽然看上去是一样都生成了5个元素,但是
前者是一个list对象, 如果遍历的话 for item in a 就会一下子返回全部元素然后再遍历, 而后者是个Generator,
用for item in a遍历是每次只是返回一个元素, 这样的好处是省内存(在list很大的情况下)。

##python的all函数可以简化逻辑表达式有很多”与“的时候的写法
比如:

a, b, c = True, False, True
if a and b and c:
    return True
else:
    return False
可以简化成:
    return all([a, b, c])

由此可以看到all函数的作用是判断当且仅当参数里面都为真的时候返回真, 否则返回假。

但是这里更深入的话涉及all的判断顺序和传入的参数是list还是iterable对象是不同的。

MTV 开发模式

发表于 2015-04-15 | 分类于 Django

我们来花点时间考虑下 Django 数据驱动 Web 应用的总体设计。

我们在前面章节提到过,Django 的设计鼓励松耦合及对应用程序中不同部分的严格分割。 遵循这个理念的话,要想修改应用的某部分而不影响其它部分就比较容易了。 在视图函数中,我们已经讨论了通过模板系统把业务逻辑和表现逻辑分隔开的重要性。 在数据库层中,我们对数据访问逻辑也应用了同样的理念。

把数据存取逻辑、业务逻辑和表现逻辑组合在一起的概念有时被称为软件架构的Model-View-Controller (MVC)模式。 在这个模式中, Model 代表数据存取层,View代表的是系统中选择显示什么和怎么显示的部分,Controller 指的是系统中根据用户输入并视需要访问模型,以决定使用哪个视图的那部分。

为什么用缩写?

像 MVC 这样的明确定义模式的主要用于改善开发人员之间的沟通。 比起告诉同事,“让我们采用抽象的数据存取方式,然后单独划分一层来显示数据,并且在中间加上一个控制它的层”,一个通用的说法会让你收益,你只需要说:“我们在这里使用MVC模式吧。”。

Django 紧紧地遵循这种 MVC 模式,可以称得上是一种 MVC 框架。 以下是 Django 中 M、V 和 C 各自的含义:

  • M ,数据存取部分,由django数据库层处理,本章(第5章)要讲述的内容。

  • V ,选择显示哪些数据要显示以及怎样显示的部分,由视图和模板处理。

  • C ,根据用户输入委派视图的部分,由 Django 框架根据 URLconf 设置,对给定 URL 调用适当的 Python 函数。

由于 C 由框架自行处理,而 Django 里更关注的是模型(Model)、模板(Template)和视图(Views),Django 也被称为 MTV 框架 。在 MTV 开发模式中:

  • M 代表模型(Model),即数据存取层。 该层处理与数据相关的所有事务: 如何存取、如何验证有效性、包含哪些行为以及数据之间的关系等。

  • T 代表模板(Template),即表现层。 该层处理与表现相关的决定: 如何在页面或其他类型文档中进行显示。

  • V 代表视图(View),即业务逻辑层。 该层包含存取模型及调取恰当模板的相关逻辑。 你可以把它看作模型与模板之间的桥梁。

如果你熟悉其它的 MVC Web开发框架,比方说 Ruby on Rails,你可能会认为 Django 视图是控制器,而 Django 模板是视图。 很不幸,这是对 MVC 不同诠释所引起的错误认识。 在 Django 对 MVC 的诠释中,视图用来描述要展现给用户的数据;不是数据 如何展现 ,而且展现 哪些 数据。 相比之下,Ruby on Rails 及一些同类框架提倡控制器负责决定向用户展现哪些数据,而视图则仅决定 如何 展现数据,而不是展现 哪些 数据。

两种诠释中没有哪个更加正确一些。 重要的是要理解底层概念。

django-locals()技巧

发表于 2015-04-15 | 分类于 Django

思考一下我们对 current_datetime 的最后一次赋值:

def current_datetime(request):
    now = datetime.datetime.now()
    return render_to_response('current_datetime.html', {'current_date': now})

很多时候,就像在这个范例中那样,你发现自己一直在计算某个变量,保存结果到变量中(比如前面代码中的 now ),然后将这些变量发送给模板。 尤其喜欢偷懒的程序员应该注意到了,不断地为临时变量和临时模板命名有那么一点点多余。 不仅多余,而且需要额外的输入。

如果你是个喜欢偷懒的程序员并想让代码看起来更加简明,可以利用 Python 的内建函数 locals() 。它返回的字典对所有局部变量的名称与值进行映射。 因此,前面的视图可以重写成下面这个样子:

def current_datetime(request):
    current_date = datetime.datetime.now()
    return render_to_response('current_datetime.html', locals())

在此,我们没有像之前那样手工指定 context 字典,而是传入了 locals() 的值,它囊括了函数执行到该时间点时所定义的一切变量。 因此,我们将 now 变量重命名为 current_date ,因为那才是模板所预期的变量名称。 在本例中, locals() 并没有带来多 大 的改进,但是如果有多个模板变量要界定而你又想偷懒,这种技术可以减少一些键盘输入。

使用 locals() 时要注意是它将包括 所有 的局部变量,它们可能比你想让模板访问的要多。 在前例中, locals() 还包含了 request 。对此如何取舍取决你的应用程序。

Django载入模板的顺序

发表于 2015-04-15 | 分类于 Django

Django默认会在配置文件setting.py的TEMPLATE_LOADERS中开启django.template.loaders.filesystem.Loader,开启该选项后可以按照TEMPLATE_DIRS中列出的路径的先后顺序从中查找并载入模板。

比如有如下配置:

TEMPLATE_LOADERS = (
    'django.template.loaders.filesystem.Loader',
)
TEMPLATE_DIRS = (
    '/var/www/site/mycitsm/mycitsm/templates',
    '/var/www/site/mycitsm/sqlreview/templates',
)

现在TEMPLATE_DIRS中指定的两个目录中均存在base.html,渲染模板的语句为 return render(request, 'base.html',context),那么Django会优先使用第一个目录中的base.html模板。当第一个目录中不存在base.html时,Django才会使用第二个目录中的base.html模板。当然,当两个目录都不存在base.html时,会提示找不到模板。因此为了避免混淆,在使用'django.template.loaders.filesystem.Loader'时尽量不要在TEMPLATE_DIRS指定的不同的位置放置同名模板。

如果确实想在不同的位置放置同名模板呢?比如,为了达到程序复用的目的,我们往往会创建一些某个Django APP特定的static文件和template文件,保存在该APP特定的目录中。而我们不能保证这些文件与其他位置的文件不发生重名。因此这里引入了另外一种模板载入模式'django.template.loaders.app_directories.Loader',开启该选项后可以从INSTALLED_APPS中已安装app对应的templates/目录中查找要渲染的模板文件(对于静态文件对应的是app的static/目录)。

比如有如下配置:

TEMPLATE_LOADERS = (
   'django.template.loaders.app_directories.Loader',
)
TEMPLATE_DIRS = ()

这里我们没有在TEMPLATE_DIRS 中指定包含模板文件的路径信息,但由于我们使用的是 'django.template.loaders.app_directories.Loader'载入方式,他会自动从APP对应的templates目录中查找相应的模板文件。比如渲染语句为return render(request, 'base.html',context),APP对应的模板目录为/var/www/site/mycitsm/sqlreview/templates/,只要该目录中存在base.html,Django就会渲染该模板,不存在则提示找不到,除此之外不会从其他地方找该模板文件了。

细心的你可能已经想到了:要是同时使用了两种载入模板的方式呢?比如同时使用了’django.template.loaders.filesystem.Loader’和’django.template.loaders.app_directories.Loader’会如何查找并载入模板?

比如有如下配置:

TEMPLATE_LOADERS = (
    'django.template.loaders.filesystem.Loader',
    'django.template.loaders.app_directories.Loader',
)
TEMPLATE_DIRS = (
    '/var/www/site/mycitsm/mycitsm/templates',
    '/var/www/site/mycitsm/sqlreview/templates',
)

TEMPLATE_DIRS中指定的两个目录内均存在base.html模板,渲染模板的语句为 return render(request, 'base.html',context),则Django会先依据TEMPLATE_LOADERS中最先列出的模板载入方式来查找并载入模板,方式同上,若找不到模板文件则使用列出的第二种方式查找,依次类推,直至找到或找不到。这样的话,Django要么找不到模板,要么会载入最先找到的模板,若在多个不同路径下存在同名的模板文件,最终载入的模板与列出的载入方式的顺序和列出的包含模板的目录的顺序嘻嘻相关。这往往是不明确的,极易造成混淆。

因此,通常在APP各自的templates目录中保存APP特定的模板,并不直接在APP对应templates目录中直接存放模板文件本身,而是在该目录中在创建一层以APP名称命名的目录,比如APP名称为sqlreview则存放该APP模板的目录为…/sqlreview/templates/sqlreview/,在指定要渲染的模板时可以通过模板文件的上一层目录来限定模板文件,以避免混淆,这实际上是提供了一个命名空间。比如return render(request, ‘sqlreview/base.html’,context),可以在/var/www/site/mycitsm/sqlreview/templates目录中找到该模板。这样便不用担心Django载入的模板究竟是不是对的、需要的那个模板。

#django 1.8中的改变
在django 1.8中,setting里取消了TEMPLATE_LOADERS和TEMPLATE_DIRS设置,用一个TEMPLATES设置来代替

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'blog/templates'),],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

其中’DIRS’字段的值是一个列表,用来代替TEMPLATE_DIRS,设置模板的路径

TEMPLATES自带的loader是'django.template.loaders.filesystem.Loader',如果我们想设置loader为'django.template.loaders.app_directories.Loader',就将APP_DIRS的值设置为TRUE

##补充

'django.template.loaders.filesystem.Loader'依赖于’DIRS’的设置,如果’DIRS’是一个空列表的话,他将找不到任何模板

'django.template.loaders.app_directories.Loader'会自动去app下面的template目录下寻找模板,所以采用这种方法就无需给’DIRS’赋值

第一部分转载自这里

Django的时区

发表于 2015-04-15 | 分类于 Django

视乎你的机器,显示的日期与时间可能和实际的相差几个小时。 这是因为Django是有时区意识的,并且默认时区为America/Chicago。 (它必须有个值,它的默认值是Django的诞生地:美国/芝加哥)如果你处在别的时区,你需要在settings.py文件中更改这个值。请参见它里面的注释,以获得最新世界时区列表。在中国我们设置为Asia/Shanghai

scp不能拷贝符号链接

发表于 2015-04-13 | 分类于 linux

今天把一个web目录直接scp -r到另一台机器上,发现起不来,后来发现是在配置目录下的一些符号连接都没有拷贝过来。

如果要将这些符号连接拷贝过来,不能直接scp -r这个文件夹,需要先将文件夹打包,然后拷贝

python导入模块

发表于 2015-04-08 | 分类于 python

#python导入模块

##使用from import 不能节约内存

python导入模块和他们的属性有两种方法:

import random
print random.choice(range(10))

和

from random import choice
print choice(range(10))

第一种方法是将模块的名字设置为一个隐含的名字空间里的全局变量,这样你就可以好像访问全局属性那样访问choice函数。而在第二个例子里,我们是直接把choice引入到全局名字空间里来(而非模块的名字)。因此不再需要把这个属性当成是模块的成员了。实际上我们也只拥有了这个属性而已。

Python新手之间经常有一种误解,以为第二种方法只导入了一个函数,而没有导入整个模块。这是不对的。整个模块其实已经被导入了,但是只有那个函数的引用被保存了起来。所以from-import这种语法并不能带来性能上的差异,也没有节省什么内存。

##能不能重复导入一个模块

新手经常会担忧的一个问题是他们有两个模块m.py和n.py都导入了foo.py模块。当m导入n时,foo岂不是会被导入两次?简单的来说,没错,是这样的,但是和你想的有点不一样。

Python有导入模块(importing)和加载模块(loading)之分。一个模块可以被导入任意多次,但是它只会被加载一次。就是说,当Python碰到一个已经被加载的模块又被导入时,它会跳过加载的过程,所以你无需担心额外消耗内存的问题。

i++的线程安全性

发表于 2015-03-28 | 分类于 c

转载自这里

i++的线程安全性可以总结如下:

  1. 如果i是局部变量,那么是可重入的,也就是线程安全的。
  2. 如果i是全局变量,则同一进程的不同线程都可能访问到该变量,因而是线程不安全的。

上面这两点比较清晰,具体原因我将在下面解释:

本质上来讲,i并不是因为是全局变量才说是线程不安全的。其实其本质原因是i++这个操作并不是原子的,如果这是原子操作的话,具有不可分特性,那么即便是所有线程都能访问到,也都是线程安全的。

i++最终被编译后的反汇编代码大概如下:

mov eax,[xxxxxxxx]
inc   eax

一条c语句已经被分为两条操作指令,那么在这两条操作指令执行之间,可能由于中断而被调度到不同线程,于是,不安全性就产生了。当然了,这并不是说单条汇编语句就是线程安全的,这取决于CPU架构,因为单条汇编指令可能在多个CPU时钟周期内进行,有些CPU架构可以在任何一个时钟周期内响应中断,对于这样的CPU,就是单条指令都不是安全的。当然了,现在的CPU大部分都是一条指令执行完后才能响应中断的。想要进行原子操作,方法有很多种,其中一种比较简单的是在原子语句之间先关闭中断,然后进行原子操作后再打开中断就OK了。

##原子操作

所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程),不能被更高等级中断抢夺优先。

由于操作系统大部分时间处于开中断状态,所以,一个程序在执行的时候可能被优先级更高的线程中断。而有些操作是不能被中断的,不然会出现无法还原的后果,这时候,这些操作就需要原子操作。就是不能被中断的操作。

1…678…18
You Wangqiu

You Wangqiu

世之奇伟、瑰怪,非常之观,常在于险远,而人之所罕至焉,故非有志者不能至也

171 日志
21 分类
24 标签
GitHub 知乎 E-Mail
© 2018 You Wangqiu
由 Hexo 强力驱动
主题 - NexT.Muse