并发编程 --线程

一、线程

  • 生产者消费者模型
  • 线程介绍
  • 创建线程的两种方式
  • join方法
  • 线程间的数据共享
  • 进程对象的其他方法和属性
  • 守护进程
  • 互斥锁
  • GIL(Global Interpreter Lock)
  • GIL与普通互斥锁的区别
  • 死锁与递归锁
  • 信号量
  • event事件
  • 线程queue

一、线程

1.1 生产者消费者模型
"""
生产者:做包子(生产数据的)
消费者:吃包子(处理数据的)

进入的队列,用来解决供需不平衡的问题
    定义一个队列,用来存放固定数量的数据,因此,
    生产者和消费者不需要直接进行接触,就可以完成整个过程(生产-消费-供需)
"""
from multiprocessing import Queue, Process, JoinableQueue
import time

def producer(name, food, q):
    for i in range(5):
        data = '%s 生产了%s %s' % (name, food, i)
        time.sleep(1)
        print(data)
        q.put(data)  # 将生产者的数据放入队列中

def consumer(name, q):
    while True:
        data = q.get()
        if data is None:
            break
        time.sleep(1.5)
        print('%s 吃了 %s' % (name, data))
        q.task_done()  # 告诉你的队列,你已经将数据取出并且处理完毕

if __name__ == '__main__':
    q = JoinableQueue()  # 实例化一个队列对象
    p1 = Process(target=producer, args=('zs', '包子', q))
    p2 = Process(target=producer, args=('ls', '馒头', q))
    c1 = Process(target=consumer, args=('ww', q))
    c2 = Process(target=consumer, args=('ll', q))

    p1.start()
    p2.start()

    c1.daemon = True
    c2.daemon = True

    c1.start()
    c2.start()
    # 等待生产者生产完所有的数据
    p1.join()
    p2.join()
    # 在生产者生产完数据之后,往队列里面放一个提示性的消息,
    # 告诉消费者已经没有数据,无需再等待

    q.join()  # 等待队列中数据全部取出
    print('主')

1.2 线程介绍
将进程比喻成车间,那么线程就可以当作车间里的一条生产线
一个进程中至少有一个线程
1.3 创建线程的两种方式
from threading import Thread
import time

# -----------方式一: 通过调用函数------------------
def task(name):
    print('%s is starting' % name)
    time.sleep(2)


if __name__ == '__main__':
    t = Thread(target=task, args=('li',))
    t.start()
    print('主')

# li is starting
# 主

# -----------方式二:通过类------------------------
class MyThread(Thread):
    def __init__(self, name):
        super().__init__()
        self.name = name

    def run(self):
        print('%s is running' % self.name)
        time.sleep(1)
        print('%s is over' % self.name)

if __name__ == '__main__':
    t = MyThread('zhangsan')
    t.start()
    print("主")
1.4 join方法
与进程的join方法类似,同样是为了在线程执行完才进行其他的操作
1.5 线程间的数据共享
from threading import Thread

x = 100
def task():
    global x
    x = 666

t = Thread(target=task)
t.start()
t.join()
print(x)  # 666
1.6 进程对象的其他方法和属性
from threading import Thread, active_count, current_thread
import os
import time

def task(name):
    print("%s is running" % name, current_thread().name, current_thread().getName())
    time.sleep(1)
    print('%s is over' % name)

def info(name):
    # current_thread().name :当前线程的名字 后面的 getNam一样的效果
    print('%s is running' % name, current_thread().name, current_thread().getName())
    time.sleep(1)
    print('%s is over' % name)

t = Thread(target=task, args=('zhangsan',))
t1 = Thread(target=info, args=('lisi',))
t.start()
t1.start()
t.join()  # 执行完之后才释放主进程资源

# time.sleep(3)
print(active_count())
print(os.getpid())
print('11', current_thread().name)
print('22', current_thread().getName())

"""
# 起的两个新线程
zhangsan is running Thread-1 Thread-1
lisi is running Thread-2 Thread-2
zhangsan is over  # 线程1运行完
2  # 当前存活的线程数为2,1.读取线程个数时,线程t已经结束,但是线程2和主进程的线程未结束;
    # 有时候,线程t和线程t1都运行结束,那么就会显示 1 
    # 通过在上面加time.sleep()几秒钟的时间,下面的active_count()就为1了
4124  # 获取进程id
MainThread  # 这一行与下一行,表示都是运行在主进程下的(主)线程中
MainThread
lisi is over
"""
1.7 守护进程
from threading import Thread
import time

def task(name):
    print('%s is running' % name)
    time.sleep(1)
    print('%s is over' % name)

if __name__ == '__main__':
    t = Thread(target=task, args=('li', ))
    t.daemon = True
    t.start()
    print('主')
"""
# 未开启守护进程
li is running
主
li is over
"""
"""
# 开启了守护进程
li is running
主
"""
1.8 互斥锁
from threading import Thread, Lock
import time

mutex = Lock()
n = 10.

def task():
    global n
    mutex.acquire()  # 锁住
    tmp = n
    time.sleep(0.1)
    n = tmp - 1
    mutex.release()  # 释放

t_list = []
for i in range(10):
    t = Thread(target=task)
    t.start()
    t_list.append(t)

for t in t_list:
    t.join()

print(n)
1.9 GIL
GIL是一个互斥锁:
    1.保证数据的安全(以牺牲效率来换取数据的安全)
    2.阻止同一个进程内多个线程同时执行(不能并行,可并发)
    3.GIL全局解释器存在的原因是Cpython解释器的内存管理不是线程安全的

ps: 每个进程中都有垃圾回收机制

GIL全局解释器锁是所有解释性语言的通病
同一个进程下的多个线程不能实现并行,但是能够实现并发,多进程下的线程能够实现并行(有多个处理器)
Q:python多线程能够实现并行?
A:分情况讨论:
    1.四个任务:计算密集的任务(假设每个任务耗时10s)
        单核情况下:多线程好一点,消耗的资源少一点
        多核情况下:开四个进程:10s多点
                    开四个线程:40s多点
    2.四个任务:IO密集的任务(假设每个IO任务耗时10s)
        单核情况:多线程好一点
        多核情况:多线程好一点
1.10 GIL与普通互斥锁的区别
对于不同的数据,要想保证安全,需要加不同的锁处理
GIL并不能保证数据的安全,它是对cpython解释器加锁,针对的是线程,保证的是同一线程的多线程之间的安全
1.11 死锁与递归锁
1.死锁:
例子:
    A被锁在了B的家里,身上揣着自己家的钥匙
    B被锁在了A的家里,身上揣着自己家的钥匙
    A,B互相等着另一把钥匙来打开门
两个进程A,B,二者都同时需要获取数据a和b两个数据之后,才能开始完成相应的操作,
但是二者一旦运行并且A获取了数据a,B获取了数据b,A接下来便会一直等待获取数据b,
B会一直等待获取数据a,这便产生了死锁。

2.递归锁:
递归锁,调用RLock
当一个线程抢到了锁,可再次(多次)得到这个锁,并且每次得到这个锁都会对锁
进行标记+1(类似于引用计数),在标记减为0之前,其他线程都不可以操作
from threading import Thread,Lock,RLock
import time

"""
自定义锁一次acquire必须对应一次release,不能连续acquire
递归锁可以连续的acquire,每acquire一次计数加一:针对的是第一个抢到我的人
"""
import random

# mutexA = Lock()
# mutexB = Lock()
mutexA = mutexB = RLock()  # 抢锁之后会有一个计数 抢一次计数加一 针对的是第一个抢到我的人

class MyThead(Thread):
    def run(self):
        self.func1()
        self.func2()

    def func1(self):
        mutexA.acquire()
        print('%s 抢到A锁了'%self.name)
        mutexB.acquire()
        print('%s 抢到B锁了' % self.name)
        mutexB.release()
        print('%s 释放了B锁'%self.name)
        mutexA.release()
        print('%s 释放了A锁'%self.name)

    def func2(self):
        mutexB.acquire()
        print('%s 抢到了B锁'%self.name)
        time.sleep(1)
        mutexA.acquire()
        print('%s 抢到A锁了' % self.name)
        mutexA.release()
        print('%s 释放了A锁' % self.name)
        mutexB.release()
        print('%s 释放了B锁' % self.name)

for i in range(10):
    t = MyThead()
    t.start()
1.12 信号量
跟普通的互斥锁区别在于,普通的互斥锁是单个的数据的锁,所有的线程抢这一个东西
信号量是,包含多个锁的多个数据,所有的线程抢多把锁
from threading import Thread,Semaphore
import time
import random
sm = Semaphore(5)  # 五个厕所五把锁
# 跟你普通的互斥锁区别在于,普通的互斥锁是独立卫生间,所有人抢一把锁
# 信号量 公共卫生间 有多个坑,所有人抢多把锁

def task(name):
    sm.acquire()
    print('%s正在蹲坑'%name)
    # 模拟蹲坑耗时
    time.sleep(random.randint(1,5))
    sm.release()

if __name__ == '__main__':
    for i in range(20):
        t = Thread(target=task,args=('伞兵%s号'%i,))
        t.start()
2.13 event事件
例子:车等红路灯
    人为的设置条件,当满足什么条件的时候,就进行什么操作
from threading import Event,Thread
import time

event = Event()
def light():
    print('红灯亮着!')
    time.sleep(3)
    event.set()  # 解除阻塞,给我的event发了一个信号
    print('绿灯亮了!')

def car(i):
    print('%s 正在等红灯了'%i)
    event.wait()  # 阻塞
    print('%s 加油门飙车了'%i)

t1 = Thread(target=light)
t1.start()

for i in range(10):
    t = Thread(target=car,args=(i,))
    t.start()
2.14 线程queue
1.普通级别的q
2.先进后出的q
3.具有优先级的q
import queue

# 普通的q
q = queue.Queue(3)
q.put(1)
q.put(2)
q.put(3)
print(q.get())
print(q.get())
print(q.get())


# 先进后出q
q = queue.LifoQueue(5)
q.put(1)
q.put(2)
q.put(3)
q.put(4)
print(q.get())

# 优先级q
q = queue.PriorityQueue()
q.put((10, 'a'))
q.put((-1, 'b'))
q.put((100, 'c'))
print(q.get())
print(q.get())
print(q.get())

结果:
(-1, 'b')
(10, 'a')
(100, 'c')

posted @ 2019-05-12 21:37  xt12321  阅读(291)  评论(0编辑  收藏  举报