并发编程(一) 线程

一 进程与线程

进程

最小的资源管理单位(盛放线程的容器),进程之间的数据集是相互独立的

进程组成:

1.程序
2.数据集:程序执行过程中多需要使用的资源
3.进程控制块:保存进程状态

线程

最小的执行单位(容器里的东西),线程又叫做微进程,一个进程包括多个线程,共享同一个数据集

线程组成:

1.线程ID
2.程序计数器
3.寄存器集合
4.堆栈

线程的引入减小了程序并发执行时的开销,提高了操作系统的并发性能。线程没有自己的系统资源。

进程与线程的关系

进程和线程的关系:

1.一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
2.资源分配给进程,同一进程的所有线程共享该进程的所有资源。
3.CPU分给线程,即真正在CPU上运行的是线程。

并发和并行

并发:能同时解决多个问题的能力,一个cpu通过切换进程实现并发
并行:一个cpu执行一个进程,多个cpu各种执行自己的进程

操作系统并发情况下,什么时候切换进程:
	1.出现IO操作
	2.固定时间

二 Python线程

python中的threading模块,提供了线程相关操作

2.1 Thread类对象创建

直接创建

import threading
import time


def clock(n):  # 定义某个线程要运行的函数
    print('{0}\'s clock begin!'.format(n))
    time.sleep(n)
    print('{0}\'s clock time\'s up!'.format(n))


print('main threading!')

t1 = threading.Thread(target=clock, args=(10,))  # 生成一个线程实例
t2 = threading.Thread(target=clock, args=(15,))

t1.start()  # 启动线程
t2.start()

print('main threading end!')

结果:

main threading!
10's clock begin!
15's clock begin!
main threading end!
... 10秒后
10's clock time's up!
... 5秒后
15's clock time's up!

继承创建

import threading
import time


class MyThread(threading.Thread):
    def __init__(self, num):
        threading.Thread.__init__(self)
        self.num = num

    def run(self):
        print('{0}\'s clock begin!'.format(self.num))
        time.sleep(self.num)
        print('{0}\'s clock time\'s up!'.format(self.num))


print('main threading!')
t1 = MyThread(10)
t2 = MyThread(15)

t1.start()
t2.start()
print('main threading end!')

2.2 Thread类方法

基本方法

  • start()启动线程,等待CPU调度
  • join()阻塞主线程,等待子线程结束,再执行主线程(在子线程完成运行之前,这个子线程的父线程将一直被阻塞)
  • setDaemon(True)将子线程设置为守护线程,当主线程结束时,子线程也结束,主要用途:作为监听功能使用,在start()方法之前使用
  • run()线程被cpu调度后自动执行线程对象的run方法
import threading
import time


def power(n):
    print('游戏{}秒钟'.format(n))
    time.sleep(n)
    print('{}秒钟游戏结束'.format(n))


def call(n):
    print('通话{}秒钟'.format(n))
    time.sleep(n)
    print('{}秒钟通话结束'.format(n))


s = time.time()
print('开机!')

t1 = threading.Thread(target=power, args=(5,))
t2 = threading.Thread(target=call, args=(2,))

# ==== 并发 ====
t1.start()
t2.start()

t1.join()  # 线程t1,阻塞主线程
t2.join()

# ==== 串行 ====
# t1.start()
# t1.join
# t2.start()
# t2.join()

print('关机!')
print('手机运行{}秒'.format(time.time() - s))

ps:
利用调整join()方法的位置,我们可以控制子线程们是串行或并发:

  • 串行:join函数的作用是等待当前线程结束,所以每一个线程创建之后,调用start(),这时紧跟该线程的join(),那么就是顺序执行;
  • 并发:如果多个线程先完成示例化和start(),最后加上统一调用join(),那么就变成了并发。

其他方法

除上述,Thread类提供的另一些方法

  • isAlive()返回线程是否存活
  • getName()返回线程名
  • setName()设置线程名称

threading模块提供的一些方法:

  • threading.currentThread()返回当前的线程变量
  • threading.enumerate()返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程
  • threading.activeCount()返回正在运行的线程数量,与len(threading.enumerate())有相同的结果

2.3 GIL(全局解析器锁)

定义

GIL(global interpreter lock):在CPython中,全局解释器锁是一种互斥锁,防止多个本地线程执行Python字节码。 这个锁是必要的,因为CPython的内存管理不是线程安全的。 (但是,由于GIL存在,其他功能已经发展到依赖于它实施的保证。)

ps:GIL是加在cpython解释器上的,jpython/pypy等都没有GIL

影响

由于GIL,导致同一时刻,同一进程,只能有一个线程被执行

ps:
对于计算密集型任务:多个线程的代码很有可能是线性执行的(省掉上下文切换)
对IO密集型任务:多线程可以明显提高效率

如何避免受到GIL的影响?

用multiprocess替代Thread(multiprocess进程)

multiprocess库的出现很大程度上是为了弥补thread库因为GIL而低效的缺陷。它完整的复制了一套thread所提供的接口方便迁移。唯一的不同就是它使用了多进程而不是多线程。每个进程有自己的独立的GIL,因此也不会出现进程之间的GIL争抢。

multiprocess的本质就是开多进程,但是缺陷时开销大,切换复杂

其他解决方法还有:

  • 协程 + 多进程,其本质就是单线程 + 多进程
  • IO多路复用
  • 换C模块实现多线程

2.4 线程锁

由于操作系统是随机调度线程的执行的,当多个线程同时修改同一条数据时可能会出现脏数据,所以,出现了线程锁,保证同一时刻允许一个线程执行操作。

同步锁(Lock)

多个线程对共享资源访问时用到同步锁,线程用到资源进行acquire(上锁),资源访问完毕release(解锁)

使用方法:

l = threading.Lock() # 同步锁
	
l.acquire() # 上锁
	
# 上锁的代码
	
l.release() # 解锁

只能有一个线程拿到上锁acquire资源,上锁资源被使用完毕后,被release,下一个线程才能拿到上锁资源,其他资源不受锁的影响

import time
import threading

l = threading.Lock()


def subNum():
    global num
    # num -= 1

    l.acquire()  # 上锁

    temp = num
    time.sleep(0.01)
    num = temp - 1

    l.release()  # 解锁


num = 100

thread_list = []

for i in range(100):
    t = threading.Thread(target=subNum)
    t.start()
    thread_list.append(t)

for t in thread_list:
    t.join()

print('result:', num)

递归锁(RLock)

解决死锁问题

使用方法:

rl = threading.RLock()  # 递归锁
	
rl.acquire()  # 上锁 计数+1 counter=1
rl.acquire()  # 上锁 计数+1 counter=2
...
rl.release()  # 解锁 计数-1 counter=1
rl.release()  # 解锁 计数-1 counter=0

counter记录了acquire的次数,直到一个线程所有的acquire都被release,即count为0时,其他线程才可以访问该资源

import threading
import time

Rlock = threading.RLock()


class MyThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        self.func1()
        self.func2()

    def func1(self):
        Rlock.acquire()  # 如果锁被占用,则阻塞在这里,等待锁的释放 counter = 1
        print('I am %s ,get res: %s --- %s ' % (self.name, 'ResA', time.time()))

        Rlock.acquire()  # counter = 2
        print('I am %s ,get res: %s --- %s ' % (self.name, 'ResB', time.time()))
        Rlock.release()  # counter = 1

        Rlock.release()  # counter = 0

    def func2(self):
        Rlock.acquire()  # counter = 1
        print('I am %s ,get res: %s --- %s ' % (self.name, 'ResB', time.time()))
        time.sleep(0.2)

        Rlock.acquire()  # counter = 2
        print('I am %s ,get res: %s --- %s ' % (self.name, 'ResA', time.time()))
        Rlock.release()  # counter = 1

        Rlock.release()  # counter = 0

if __name__ == '__main__':
    print('start ----------- %s'%time.time())

    for i in range(0,10):

        mt = MyThread()
        mt.start()

2.5 事件(Event)

线程之间的通信作用

python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。

事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。

r = threading.Event()  # 相当于设置标志位flag = False
event.wait()  # 阻塞,等待falg状态,可以加参数,表示等待秒数
r.set()  # 将 flag = True
event.isSet()  # 判断False或True
import logging
import time
import threading

logging.basicConfig(level=logging.DEBUG, format='(%(threadName)-10s) %(message)s', )


def worker(event):
    logging.debug('等待redis准备…')

    while not event.isSet():
        logging.debug('等待连接...')
        event.wait(3)  # if flag = False阻塞,等待flag = True 继续执行

    logging.debug('redis准备好,并连接到redis服务器和做一些工作 %s', time.ctime())
    time.sleep(1)


def main():
    r = threading.Event()  # flag = False

    t1 = threading.Thread(target=worker, args=(r,), name='t1')
    t1.start()

    t2 = threading.Thread(target=worker, args=(r,), name='t2')
    t2.start()

    logging.debug('首先,检查redis服务器,确保它是OK,然后触发复述事件做好准备')

    time.sleep(6)
    r.set()  # flag = True


if __name__ == '__main__':
    main()

2.6 信号量(Semaphore)

互斥锁 同一时刻只能有一个线程更改数据
同时只有n个线程可以获得semaphore,即可以限制最大连接数为n)

使用方法:

s = threading.Semaphore(n) # n个连接数
	
s.acquire() 

...
	
s.release()
import threading
import time

s = threading.Semaphore(5) #同时5个线程获取semaphore


def foo():
    s.acquire()

    print('%s 获到了semaphore' % threading.current_thread().getName())
    time.sleep(2)

    s.release()


for i in range(20):
    t = threading.Thread(target=foo)
    t.start()

posted @ 2017-05-08 14:41  六神酱  阅读(166)  评论(0编辑  收藏  举报