Python小白带小白初涉多线程

Python 2.7
IDE Pycharm 5.0.3


首先

解释一下线程:简单来说,一个进程中包含多个线程,比如打开一个qq(进程),然后你一边聊qq(一个线程),一边用qq传送文件(一个线程),等等,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)

再者

引用廖雪峰大大的话是酱紫的:Python解释器由于设计时有GIL全局锁,导致了多线程无法利用多核。多线程的并发在Python中就是一个美丽的梦。
But--Python的线程是真正的Posix Thread,而不是模拟出来的线程。


好了

了解其中的大概,就可以用程序来验证自己的想法了!!
不要方,直接上栗子(好饿~)

我们知道线程并发(虽然python不支持),但是大概是这么个意思,对于进程来说,同一变量,都有备份(大家都有面包),所以不用抢,但是,线程的世界中,变量只有一个,共享!所以他们得互相抢占资源,抢着执行程序,这就导致,任何一个共享变量都可以被任何一个线程修改,看图看图。来人,上栗子~

# -*- coding: utf-8 -*-
import threading,time,os,random
balance = 0

def change_it(n):
    global balance
    balance = balance +n
    balance = balance -n

def run_thread(n):
    for i in range(200):
            start = time.time()
            change_it(n)
            #time.sleep(random.random())
            end = time.time()
    print '%s 线程结束...'%threading.current_thread().name


print '开启当前线程...当前线程为%s'%threading.current_thread().name

print '实例化抢占模式多线程...'
t1 = threading.Thread(target=run_thread,args=(1,))
t2 = threading.Thread(target=run_thread,args=(4,))
t3 = threading.Thread(target=run_thread,args=(7,))

print '实例化抢占模式多线程完成...'
print '开启抢占模式多线程...'
t1.start()
t2.start()
t3.start()

print '开启抢占模式多线程完成...'
t1.join()
t2.join()
t3.join()

print '多线程结束,输出balance :',balance

开启当前线程...当前线程为MainThread
实例化抢占模式多线程...
实例化抢占模式多线程完成...
开启抢占模式多线程...
开启抢占模式多线程完成...
Thread-1 线程结束...
Thread-2 线程结束...
Thread-3 线程结束...
多线程结束,输出balance : 6

从结果可以看出,三个线程来回抢资源,导致变量被修改的面目全非,如果不用抢占,那么balance变量会一直保持0值!
这里可见其多线程的“霸道”
BTW任何进程默认就会启动一个线程,我们把该线程称为主线程,名字是MainThread主线程又可以启动新的线程,获取当前线程名字就用threading.current_thread().name简单快捷,啦啦啦啦


但是!

如果让线程那么霸道下去可不行,无规矩不成方圆,所以得给它上把锁,让它老实点的一个个排队来,(这样不就丧失了多线程并发的优势了咩,算了,python本来就不支持并发多线程0.0),而这个锁,线程需要向组织要(acquire),用完之后再还给组织(release),这样,就像关起门来干些不可名状的事,别人在门口待着,排队来,然后一个线程完事,再下一个线程来(捂脸)。不说了,上栗子!

# -*- coding: utf-8 -*-
import threading,time,os,random
lock = threading.Lock()
balance = 0

def change_it(n):
    global balance
    balance = balance +n
    balance = balance -n

def run_thread(n):
    for i in range(3):
        lock.acquire()
        try:
            start = time.time()
            change_it(n)
            time.sleep(random.random())
            end = time.time()
            print '%s线程在进行调用此函数! 耗时%.2f秒'%(threading.current_thread().name,end-start)
        finally:
            lock.release()
    print '%s 线程结束...'%threading.current_thread().name


print '开启当前线程...当前线程为%s'%threading.current_thread().name

print '实例化Lock模式多线程...'
t1 = threading.Thread(target=run_thread,args=(1,))
t2 = threading.Thread(target=run_thread,args=(2,))
t3 = threading.Thread(target=run_thread,args=(3,))

print '实例化Lock模式多线程完成...'
print '开启Lock模式多线程...'
t1.start()
t2.start()
t3.start()

print '开启Lock模式多线程完成...'
t1.join()
t2.join()
t3.join()

print '多线程结束,输出balance :',balance
开启当前线程...当前线程为MainThread
实例化Lock模式多线程...
实例化Lock模式多线程完成...
开启Lock模式多线程...
开启Lock模式多线程完成...
Thread-1线程在进行调用此函数! 耗时0.22秒
Thread-2线程在进行调用此函数! 耗时0.54秒
Thread-3线程在进行调用此函数! 耗时0.42秒
Thread-1线程在进行调用此函数! 耗时0.63秒
Thread-2线程在进行调用此函数! 耗时0.33秒
Thread-3线程在进行调用此函数! 耗时0.78秒
Thread-1线程在进行调用此函数! 耗时0.11秒
Thread-1 线程结束...
Thread-2线程在进行调用此函数! 耗时0.25秒
Thread-2 线程结束...
Thread-3线程在进行调用此函数! 耗时0.09秒
Thread-3 线程结束...
多线程结束,输出balance : 0

结果显示,当线程达到循环执行次数,会结束自己调用的程序,这里我把它打印出来了,会更利于理解。
不清楚的话我再贴一下lock定义:简单说,就是一个线程得到锁后,别的线程不能用!

threading.Lock() 
A factory function that returns a new primitive lock object. Once a thread has acquired it, subsequent attempts to acquire it block, until it is released; any thread may release it.


接下里的是各自为战local,不是lock锁门干事!
在多线程的环境下,每个线程都有自己的数据,为了使线程之间互不干扰,而且只能自己可见,那就要使用threading.local()了,老样子,给波简单官腔;

class threading.local 
A class that represents thread-local data. Thread-local data are data whose values are thread specific. To manage thread-local data, just create an instance of local (or a subclass) and store attributes on it:
mydata = threading.local()
mydata.x = 1

好啦好啦,就这个,然后用起来就知道具体的操作了,举栗子咯

# -*- coding: utf-8 -*-
import threading
local_name = threading.local()
local_mission = threading.local()
#创建全局ThreadLocal对象

def Task():
    print 'please read your misiion:'
    print 'Hello,%s,your mission:%s(from%s)'%(local_name.name,local_mission.mission,threading.current_thread().name)
    print '%s accept the challenge'%local_name.name
    #先后进入Task

def process_thread(name,mission):
    local_name.name = name
    local_mission.mission = mission
    #注意参数的传递
    Task()

t1 = threading.Thread(target=process_thread,args=('BOb','kill Alex'),name='专线1')
t2 = threading.Thread(target=process_thread,args=('Alex','kill Bob'),name='专线2')
t1.start()
t2.start()
t1.join()
t2.join()
print 'game over'
please read your misiion:
Hello,BOb,your mission:kill Alex(from专线1)
BOb accept the challenge
please read your misiion:
Hello,Alex,your mission:kill Bob(from专线2)
Alex accept the challenge
game over

这里的local_name(同理local_mission)就是一个ThreadLocal对象,每个Thread对它都可以读写name属性,但互不影响。你可以把local_name看成全局变量,但每个属性如local_name.name都是线程的局部变量,可以任意读写而互不干扰,也不用管理锁的问题,ThreadLocal内部会处理。


最后

进程VS线程啦,墙裂推荐进程VS线程,秒懂
懒得看我就总结下:
关于进程:
多进程模式最大的优点就是稳定性高,因为一个子进程崩溃了,不会影响主进程和其他子进程。(当然主进程挂了所有进程就全挂了,但是Master进程只负责分配任务,挂掉的概率低)著名的Apache最早就是采用多进程模式。
多进程模式的缺点是创建进程的代价大,在内存和CPU的限制下,如果有几千个进程同时运行,操作系统连调度都会成问题。


关于线程:
多线程模式通常比多进程快一点,但是也快不到哪去,而且,多线程模式致命的缺点就是任何一个线程挂掉都可能直接造成整个进程崩溃,因为所有线程共享进程的内存。
在Windows下,多线程的效率比多进程要高,所以微软的IIS服务器默认采用多线程模式。由于多线程存在稳定性的问题,IIS的稳定性就不如Apache。


最后的最后

Python适合IO密集型任务,如Web应用,让Python处理计算密集型任务你咋不上天呢!那么优美的语言干那么底层的事,甩锅给C,它行它上!


9.25补充

如何避免受到GIL的影响。
使用multiprocessing代替Thread,有人会说,诶,这个不是用在多进程上么?对的,它完整的复制了一套thread所提供的接口方便迁移,使每个进程都有自己独立的GIL,这样就不会出现进程之间的GIL争抢。
当然multiprocessing也不是万能良药。它的引入会增加程序实现时线程间数据通讯和同步的困难。就拿计数器来举例子,如果我们要多个线程累加同一个变量,对于thread来说,申明一个global变量,用thread.Lock的context包裹住三行就搞定了。而multiprocessing由于进程之间无法看到对方的数据,只能通过在主线程申明一个Queue,put再get或者用share memory的方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容