Python中的线程

一、线程的概念 

        一个进程里面至少有一个控制线程,进程的概念只是一种抽象的概念,真正在CPU上面调度的是进程里面的线程,就好比真正在地铁这个进程里面工作的实际上是地铁里面的线程,北京地铁里面至少要有一个线程,线程是真正干活的,线程用的是进程里面包含的一堆资源,线程仅仅是一个调度单位,不包含资源。


       什么时候需要开启多个线程:一个进程里面的多个线程共享这个进程里面的资源,因此如果多个任务共享同一块资源的时候,需要开启多个线程。 多线程指的是,在一个进程中开启多个线程,简单的说:如果多个任务共用同一个资源空间,那么必须在一个进程内开启多个线程。一个进程这个任务里面可能对应多个分任务,如果一个进程里面只开启一个线程的话,多个分任务之间实际上是串行的执行效果,即一个程序里面只含有一条执行路径。


       对于计算密集型应用,应该使用多进程;对于IO密集型应用,应该使用多线程。线程的创建比进程的创建开销小的多。

二、Python中线程的特点 

      在其他语言当中,一个进程里面开启多个线程,每个线程都可以给一个cpu去使用,但是在 python当中,在同一时刻,一个进程当中只能有一个线程处于运行状态。
 
     比如在其他语言当中,比如我现在开启了一个进程,这个进程当中含有几个线程,如果我现在有多个cpu,每一个线程是可以对应相应的CPU的。 

       但是在python当中,如果我们现在开启了一个进程,这个进程里面对应多个线程,同一时刻只有一个 
线程可以处于运行状态。 对于其他语言而言,在多CPU系统中,为了最大限度的利用多核,可以开启多个线程。 
但是Python中的多线程是利用不了多核优势的。
      
       在同一个进程当中,多个线程彼此之间可以相互通信;但是进程与进程之间的通信必须基于IPC这种 消息的通信机制(IPC机制包括队列和管道)。 在一个进程当中,改变主线程可能会影响其它线程的行为,但是改变父进程并不会影响其它子进程的行为,因为进程与进程之间是完全隔离的。 在python当中,在同一时刻同一进程当中只能同时有一个线程在运行,如果有一个线程使用了系统调用而阻塞,那么整个进程都会被挂起。


三、Python中进程池的相关概念

重要功能:

def apply(self, func, args=(), kwds={}):
    '''
    Equivalent of `func(*args, **kwds)`.
    '''
    assert self._state == RUN
    return self.apply_async(func, args, kwds).get()

作用:
在进程池中同步(打电话)的提交任务,若提交的前一个任务没有执行完,后一个任务则不能执行.
此时进程池中的任务将变为串行的效果.


def apply_async(self, func, args=(), kwds={}, callback=None,
        error_callback=None):
    '''
    Asynchronous version of `apply()` method.
    '''
    if self._state != RUN:
        raise ValueError("Pool not running")
    result = ApplyResult(self._cache, callback, error_callback)
    self._taskqueue.put(([(result._job, None, func, args, kwds)], None))
    return result


作用:
1.在进程池中异步(发短信)的提交任务,若提交的前一个任务没有执行完,后一个任务可以继续执行.
此时进程池中的任务将变为并行的效果.
2.在进程池当中虽然可以创建出同步对象,但是进程池当中最多只能同步的执行num个任务.
3.在进程池当中对象创建的顺序就是任务提交的顺序,但是创建之后并不一定得到执行,进程池中始终维护
num个任务在执行。
4.进程池当中始终共享着开始创建的那num个进程,减小了创建进程的开销.
5.向进程池中提交任务的顺序是一定的,但是进程池中任务执行的顺序是不一定的。


pool = Pool(processes=4):
Pool([numprocess  [,initializer [, initargs]]]):创建进程池
其中numprocess代表要创建的进程数,如果省略,将默认使用cpu_count()的值。

四、Python多线程创建

在Python中,同样可以实现多线程,有两个标准模块thread和threading,不过我们主要使用更高级的threading模块。使用例子:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

import threading

import time

def target():

    print 'the curent threading  %s is running' % threading.current_thread().name

    time.sleep(1)

    print 'the curent threading  %s is ended' % threading.current_thread().name

print 'the curent threading  %s is running' % threading.current_thread().name

t = threading.Thread(target=target)

t.start()

t.join()

print 'the curent threading  %s is ended' % threading.current_thread().name

输出:

the curent threading  MainThread is running

the curent threading  Thread-1 is running

the curent threading  Thread-1 is ended

the curent threading  MainThread is ended

start是启动线程,join是阻塞当前线程,即使得在当前线程结束时,不会退出。从结果可以看到,主线程直到Thread-1结束之后才结束。

Python中,默认情况下,如果不加join语句,那么主线程不会等到当前线程结束才结束,但却不会立即杀死该线程。如不加join输出如下:

1

2

3

4

the curent threading  MainThread is running

the curent threading  Thread-1 is running

the curent threading  MainThread is ended

the curent threading  Thread-1 is ended

但如果为线程实例添加t.setDaemon(True)之后,如果不加join语句,那么当主线程结束之后,会杀死子线程。

代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

import threading

import time

def target():

    print 'the curent threading  %s is running' % threading.current_thread().name

    time.sleep(4)

    print 'the curent threading  %s is ended' % threading.current_thread().name

print 'the curent threading  %s is running' % threading.current_thread().name

t = threading.Thread(target=target)

t.setDaemon(True)

t.start()

t.join()

print 'the curent threading  %s is ended' % threading.current_thread().name

输出如下:

the curent threading  MainThread is running

the curent threading  Thread-1 is runningthe curent threading  MainThread is ended

如果加上join,并设置等待时间,就会等待线程一段时间再退出:

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

import threading

import time

def target():

    print 'the curent threading  %s is running' % threading.current_thread().name

    time.sleep(4)

    print 'the curent threading  %s is ended' % threading.current_thread().name

print 'the curent threading  %s is running' % threading.current_thread().name

t = threading.Thread(target=target)

t.setDaemon(True)

t.start()

t.join(1)

输出:

the curent threading  MainThread is running

the curent threading  Thread-1 is running

the curent threading  MainThread is ended

主线程等待1秒,就自动结束,并杀死子线程。如果join不加等待时间,t.join(),就会一直等待,一直到子线程结束,输出如下:

the curent threading  MainThread is running

the curent threading  Thread-1 is running

the curent threading  Thread-1 is ended

the curent threading  MainThread is ended

上海python培训  shsxt.com/python


线程与锁交互示意图 

五、线程锁和ThreadLocal

(1)线程锁

对于多线程来说,最大的特点就是线程之间可以共享数据,那么共享数据就会出现多线程同时更改一个变量,使用同样的资源,而出现死锁、数据错乱等情况。

假设有两个全局资源,a和b,有两个线程thread1,thread2. thread1占用a,想访问b,但此时thread2占用b,想访问a,两个线程都不释放此时拥有的资源,那么就会造成死锁。

对于该问题,出现了Lock。 当访问某个资源之前,用Lock.acquire()锁住资源,访问之后,用Lock.release()释放资源。

1

2

3

4

5

6

7

8

9

10

11

12

13

a = 3

lock = threading.Lock()

def target():

    print 'the curent threading  %s is running' % threading.current_thread().name

    time.sleep(4)

    global a

    lock.acquire()

    try:

        a += 3

    finally:

        lock.release()

    print 'the curent threading  %s is ended' % threading.current_thread().name

    print 'yes'

用finally的目的是防止当前线程无线占用资源。

(2)ThreadLocal

介绍完线程锁,接下来出场的是ThreadLocal。当不想将变量共享给其他线程时,可以使用局部变量,但在函数中定义局部变量会使得在函数之间传递特别麻烦。ThreadLocal是非常牛逼的东西,它解决了全局变量需要枷锁,局部变量传递麻烦的两个问题。通过在线程中定义:
local_school = threading.local()
此时这个local_school就变成了一个全局变量,但这个全局变量只在该线程中为全局变量,对于其他线程来说是局部变量,别的线程不可更改。 def process_thread(name):# 绑定ThreadLocal的student: local_school.student = name

这个student属性只有本线程可以修改,别的线程不可以。代码:

1

2

3

4

5

6

7

8

9

10

11

local = threading.local()

def func(name):

    print 'current thread:%s' % threading.currentThread().name

    local.name = name

    print "%s in %s" % (local.name,threading.currentThread().name)

t1 = threading.Thread(target=func,args=('haibo',))

t2 = threading.Thread(target=func,args=('lina',))

t1.start()

t2.start()

t1.join()

t2.join()

从代码中也可以看到,可以将ThreadLocal理解成一个dict,可以绑定不同变量。

ThreadLocal用的最多的地方就是每一个线程处理一个HTTP请求,在Flask框架中利用的就是该原理,它使用的是基于Werkzeug的LocalStack。


感谢您阅读上海尚学堂python文章,获取更多内容或支持请点击 上海python培训

猜你喜欢

转载自my.oschina.net/u/3643112/blog/1635448