进程是资源的拥有者,进程占有的资源较大,因此需要引入线程。进程是资源分配的最小单位,线程是CPU调度的最小单位。进程包含线程,一个进程至少有一个线程。

创建线程示例如下

from threading import Thread
import time

def eat(num):
    for i in range(num):
        print("进餐{}".format(i))
        time.sleep(1)
def watchTV(num):
    for i in range(num):
        print("追剧{}".format(i))
        time.sleep(1)

if __name__ == '__main__':
    t1 = Thread(target=eat,args=(3,))
    t2 = Thread(target=watchTV,args=(3,))

    t1.start()
    t2.start()
    print("结束")
"""
进餐0
追剧0
结束
进餐1
追剧1
进餐2
追剧2
"""

这时有一个主线程和t1,t2线程。  

.join方法的使用

如上代码结果,两个线程是同时执行的,如果需要一个线程执行完成后,再让另一个线程执行,可以用join()方法。 

from threading import Thread
import time

def eat(num):
    for i in range(num):
        print("进餐中")
        time.sleep(1)
def watchTV(num):
    for i in range(num):
        print("看电视")
        time.sleep(1)
if __name__ == '__main__':
    t1 = Thread(target=eat,args=(3,))
    t2 = Thread(target=watchTV,args=(3,))

    t1.start()
    t1.join()
    t2.start()
    print("结束")
"""
进餐中
进餐中
进餐中
看电视
结束
看电视
看电视
"""

 .setDaemon()方法

setDaemon()可以将当前线程设置成守护线程来守护主线程,当主线程结束后,守护线程也立即结束,无论守护线程是否执行。例如QQ结束之时,打开的QQ多人聊天窗口也将关闭。

from threading import Thread
import time

def eat(num):
    for i in range(num):
        print("进餐中")
        time.sleep(1)
def watchTV(num):
    for i in range(num):
        print("看电视")
        time.sleep(1)
if __name__ == '__main__':
    t1 = Thread(target=eat,args=(3,))
    t2 = Thread(target=watchTV,args=(3,))

    t1.setDaemon(True)
    t2.setDaemon(True)
    t1.start()
    t2.start()
    print("结束")
"""
进餐中
看电视
结束
"""

实例方法

获取线程名:getName()
设置线程名:setName()
判断线程是否存活:isAlive()

自定义线程

编写MyThread类,让MyThread继承自threading的Thread类。然后重写run()方法。

from threading import Thread

class MyThread(Thread):
    def __init__(self,num):
        Thread.__init__(self)
        self.num = num
    def run(self):
        for i in range(self.num):
            print("第{}次执行".format(i))
if __name__ == '__main__':
    t1 = MyThread(3)
    t1.start()

"""
第0次执行
第1次执行
第2次执行
"""


.threading模块提供的方法

threading.currentThread() :返回当前线程的变量。
threading.enumerarte() : 返回正在运行的线程的列表。
threading.activeCount() :返回正在运行的线程数。

 

线程之间共享全局变量

线程中的全局变量可以共享,并且每个线程都可以更改全局变量

from threading import Thread

g_num = 0
def test1():
    global g_num
    g_num += 1
    print("test1的g_num是{}".format(g_num))
def test2():
    global g_num
    g_num += 10
    print("test2的g_num是{}".format(g_num))
if __name__ == '__main__':
    t1 = Thread(target=test1)
    t2 = Thread(target=test2)
    t1.start()
    t2.start()
    print("现在g_num的值为{}".format(g_num))

"""
test1的g_num是1
test2的g_num是11
现在g_num的值为11
"""

 

共享全局变量带来的问题

上述代码中我们的每个线程只对数据进行了一次更改,并没有出现什么异常,但是当每个线程对数据更改几百万几千万次的时候就会出现问题。当两个线程同一时间更改数据的时候,数据只记录其中一个线程对它的更改,因此就会出现数据不准确的问题。 如下代码,结果按理说最终结果应该为20000000,但是由于共享全局变量带来的问题,导致结果小于20000000。

多线程共享全局变量带来的计算出错问题,我们可以通过同步的方式来解决。

from threading import Thread
import time

g_num = 0
def test1(num):
    global g_num
    for i in range(num):
        g_num += 1
    print("test1的g_num是{}".format(g_num))
def test2(num):
    global g_num
    for i in range(num):
        g_num += 1
    print("test2的g_num是{}".format(g_num))
if __name__ == '__main__':
    t1 = Thread(target=test1,args=(10000000,))
    t2 = Thread(target=test2,args=(10000000,))
    t1.start()
    t2.start()

    time.sleep(3)
    print("现在g_num的值为{}".format(g_num))


"""
test1的g_num是11832117
test2的g_num是12016733
现在g_num的值为12016733
"""


 插入:同步异步的概念

同步不是同时进行的意思,同步是协同步调,有先后顺序的执行,比如A只向B表白,只有B接受之后,A才可以谈恋爱,B不接受,A就不能谈恋爱。这是两者之间具有强联系,A必须获得B的信号或者信息之后才能往下执行

异步就像平常QQ聊天一样,你要是回信息咱们就聊天,要是不回信息的话,我就做别的事情。这样的话依赖性较小。

互斥锁

当多个线程争夺一个共享数据的时候,我们同步的一种方法就是使用互斥锁。

当某个线程需要修改共享数据时,先对数据进行锁定,这样其他的线程就无法对该数据进行更改,等到该线程对数据修改完毕之后再释放数据,这样其他线程就能获得该数据,然后对该数据进行操作。这样可以保证数据的安全。

在上锁和解锁之前需要创建一个全局对象,这个对象就是锁子,然后对线程中的数据进行上锁和解锁操作。

from threading import Thread
import time
import threading

g_num = 0
def test1(num):
    global g_num
    lock.acquire()
    for i in range(num):
        g_num += 1
    lock.release()

    print("test1的g_num是{}".format(g_num))
def test2(num):
    global g_num
    lock.acquire()
    for i in range(num):
        g_num += 1
    lock.release()
    print("test2的g_num是{}".format(g_num))
lock = threading.Lock()

if __name__ == '__main__':
    t1 = Thread(target=test1,args=(10000000,))
    t2 = Thread(target=test2,args=(10000000,))
    t1.start()
    t2.start()
    time.sleep(3)
    print("现在g_num的值为{}".format(g_num))

"""
test1的g_num是10000000
test2的g_num是20000000
现在g_num的值为20000000
"""

死锁

锁之间的嵌套可能会出现死锁, 如果两个线程同时占有一部分资源,并且同时等待对方的资源,这个时候就会出现死锁。就像两个人每个人手里拿了一把锁,每把锁都需要两把钥匙才能打开,但是这时候两个人手中各有一把钥匙,两把钥匙放到一块儿才能打开锁子,但是A不给B钥匙,B也不给A钥匙,他们谁也打不开手中的那个需要两把钥匙才能打开的锁子。

from threading import Thread
import time
import threading

g_num = 0
def test1(num):
    global g_num
    lock1.acquire()
    print("test1第一层")
    lock1.acquire()
    print("test1第二层")
    for i in range(num):
        g_num += 1
    lock1.release()
    lock1.release()

    print("test1的g_num是{}".format(g_num))
def test2(num):
    global g_num
    lock2.acquire()
    print("test2第一层")
    lock2.acquire()
    print("test2第二层")
    for i in range(num):
        g_num += 1
    lock2.release()
    lock2.release()
    print("test2的g_num是{}".format(g_num))
lock1 = threading.Lock()
lock2 = threading.Lock()

if __name__ == '__main__':
    t1 = Thread(target=test1,args=(10000000,))
    t2 = Thread(target=test2,args=(10000000,))
    t1.start()
    t2.start()
    time.sleep(3)
    print("现在g_num的值为{}".format(g_num))

"""
test1第一层
test2第一层
现在g_num的值为0
"""


线程队列

import queue 导入模块
q = queue.Queue(maxsize=10)   创建队列
q.put(1)    在队列中放入数据
q.get()      删除并返回,获取不到数据就一直等待
q.qsize()  返回队列的大小
q.empty() 判断队列是否为空
q.full()      判断队列是否满
q.put_nowait(item)   放不进数据直接抛出异常
q.get_nowait(item)   得不到数据直接抛出异常
q.join()    收到q.task_done()信号再往下执行,否则一直等待。


生产者消费者模式

并发编程中,如果生产者速度很慢,那么消费者需要等待生产者。消费者如果慢的话,就需要生产者就要等待消费者。

这个时候就需要生产者和消费者之间不进行直接通讯,而是通过一个队列来进行通讯,生产者生产完后数据后直接扔到队列中,而消费者需要数据就直接从队列中取。

from threading import Thread
import queue

q = queue.Queue()
def product(name):
    count = 1
    while count <= 100:
        q.join()
        q.put(count)
        print("{}正在生产第{}个数据".format(name,count))
        count+=1


def consume(name):
    count = 1
    while count <= 100:
        data = q.get()
        print("{}正在获取第{}个数据".format(name,data))
        q.task_done()
if __name__ == '__main__':
    t1 = Thread(target=product,args=("生产者",))
    t2 = Thread(target=consume,args=("消费者",))
    t1.start()
    t2.start()

"""
生产者正在生产第1个数据
消费者正在获取第1个数据
生产者正在生产第2个数据
消费者正在获取第2个数据
生产者正在生产第3个数据
消费者正在获取第3个数据
......
生产者正在生产第99个数据
消费者正在获取第99个数据
生产者正在生产第100个数据
消费者正在获取第100个数据
"""
q.join()                等待task_done()发送信号
q.task_done()     取完发送信号

 

GIL(global interpreter lock)全局解释锁