一 创建线程

import  threading
import time
def job():
    print("这是一个需要执行的任务")
    # 激活的线程个数
    print("当前线程的个数:", threading.active_count())
    # 打印当前线程的详细信息
    print("当前线程信息:", threading.current_thread())
    time.sleep(10)

if __name__ == "__main__":
    job()

Python在主线程中创建一个子线程 python 创建一个线程_子线程

二 创建多线程

1._thread方式创建多线程

import _thread
import threading
import time

def job(name):
    print("这是一个需要执行的任务")
    # # 激活的线程个数
    # print("当前线程的个数:", threading.active_count())
    # # 打印当前线程的详细信息
    # print("当前线程信息:", threading.current_thread())
    print(name, time.ctime())
    time.sleep(2)
if __name__ == "__main__":
    # 创建多个线程, 但是没有开始执行任务;
    _thread.start_new_thread(job,('thread1', ))
    _thread.start_new_thread(job,('thread2', ))
    while True:
        pass

Python在主线程中创建一个子线程 python 创建一个线程_Python在主线程中创建一个子线程_02


2.threading方式创建多线程

import _thread
import threading
import time
def job(name):
    print('这是一个需要执行的任务: %s' %(name))
    #激活的线程个数
    print('当前线程的个数:',threading.active_count())
    #打印当前线程的详细信息
    print('当前线程信息:',threading.current_thread())
    time.sleep(3)
    print(name,time.ctime())
if __name__=='__main__':
    job('job0')
    #创建多个线程
    t1=threading.Thread(target=job,name='job1',args=('job1-name',))
    t1.start()
    t2=threading.Thread(target=job,name='job2',args=('job2-name',))
    t2.start()
    print('hello')

Python在主线程中创建一个子线程 python 创建一个线程_实例化_03

三 多线程的JOIN方法

不使用多线程

import time
def music(name):
    for i in range(2):
        print('正在听音乐%s' %(name))
        time.sleep(1)
def code(name):
    for i in range(2):
        print('正在编写代码%s' %(name))
        time.sleep(2)
if __name__=='__main__':
    start_time=time.time()
    music('That girl')
    code('爬虫')
    print('花费时间:%s' %(time.time()-start_time))

Python在主线程中创建一个子线程 python 创建一个线程_Python在主线程中创建一个子线程_04


结论:事情是一个一个的做,没有同时进行,但是我敲代码的时候是会听歌的,因此此处可以使用多线程来处理的

使用多线程

import threading
import time
def music(name):
    for i in range(2):
        print('正在听音乐%s' %(name))
        time.sleep(1)
def code(name):
    for i in range(2):
        print('正在编写代码%s' %(name))
        time.sleep(2)
if __name__=='__main__':
    start_time=time.time()
    t1=threading.Thread(target=music,args=('That girl',))
    t2=threading.Thread(target=code,args=('爬虫',))
    t1.start()
    t2.start()
    print('花费时间:%s' %(time.time()-start_time))

Python在主线程中创建一个子线程 python 创建一个线程_多线程_05


结论:发现时间确实是变快了好多,但是感觉跟想象的不太一样,执行时间都结束了,而事情却没做完,这是因为主线程阻塞了子线程进行,接着我们需要引入join方法

import threading
import time
def music(name):
    for i in range(2):
        print('正在听音乐%s' %(name))
        time.sleep(1)
def code(name):
    for i in range(2):
        print('正在编写代码%s' %(name))
        time.sleep(2)
if __name__=='__main__':
    start_time=time.time()
    t1=threading.Thread(target=music,args=('That girl',))
    t2=threading.Thread(target=code,args=('爬虫',))
    t1.start()
    t2.start()
    #等待所有的子线程执行结束之后,继续执行主线程的内容
    t1.join()
    t2.join()
    print('花费时间:%s' %(time.time()-start_time))

Python在主线程中创建一个子线程 python 创建一个线程_多线程_06


join方法是使程序等待所有的子线程执行结束之后, 继续执行主线程的内容,即等待,直到t1线程执行结束;其间阻塞正在调用的线程。

四 threading的set_daemon方法

当主线程执行结束, 让没有执行的线程强制结束;set_daemon

import threading
import time
#任务一
def music(name):
    for i in range(2):
        print('正在听音乐%s' %(name))
        time.sleep(1)
#任务二
def code(name):
    for i in range(2):
        print('正在编写代码%s' %(name))
        time.sleep(2)
if __name__=='__main__':
    start_time=time.time()
    t1=threading.Thread(target=music,args=('That girl',))
    t2=threading.Thread(target=code,args=('爬虫',))
    #将t1线程为守护线程,如果设置True,子线程启动,当主线程执行结束,子线程也结束
    #设置setDaemon必须在启动线程之前进行设置
    t1.setDaemon(True)
    t2.setDaemon(True)
    t1.start()
    t2.start()
    print('花费时间:%s' %(time.time()-start_time))

Python在主线程中创建一个子线程 python 创建一个线程_实例化_07

五 继承的方法创建多线程

1.任务无需任何参数

Python在主线程中创建一个子线程 python 创建一个线程_子线程_08


2.任务需要参数

import json
import threading
#类的继承
from urllib.error import HTTPError
from urllib.request import urlopen

import time

class IpThread(threading.Thread):
    # 重写构造方法;如果执行的任务需要传递参数,那将参数通过构造函数与self绑定;
    def __init__(self, jobname, ip):
        super(IpThread, self).__init__()
        self.jobname = jobname
        self.ip = ip
        # 将多线程需要执行的任务重写到run方法中;
    def run(self):
        try:
            # 需要有一个参数,传ip;
            url = "http://ip.taobao.com/service/getIpInfo.php?ip=%s" % (self.ip)
            # 根据url获取网页的内容, 并且解码为utf-8格式, 识别中文;
            text = urlopen(url).read().decode('utf-8')
        except HTTPError as e:
            print("Error: %s获取地理位置网络错误" %(self.ip))
        else:
            # 将获取的字符串类型转换为字典,方便处理
            d = json.loads(text)['data']
            country = d['country']
            city = d['city']
            print("%s:" % (self.ip), country, city)
def use_thread():
    start_time = time.time()
    ips = ['172.25.254.250', '8.8.8.8',
           '172.25.254.250', '8.8.8.8',
           '172.25.254.250', '8.8.8.8']
    threads = []
    for ip in ips:
        t = IpThread(jobname="爬虫", ip=ip)
        threads.append(t)
        t.start()
    # 等待所有的子线程执行结束
    [thread.join() for thread in threads]
    print("Success, 运行时间为%s" % (time.time() - start_time))

if __name__ == "__main__":
    use_thread()

Python在主线程中创建一个子线程 python 创建一个线程_子线程_09

六 线程锁

多个线程对同一个数据进行修改时,最大的危险在于把内容给改乱,如下

import threading
def add():
    global money
    for i in range(1000000):
        money+=1
def reduce():
    global money
    for i in range(1000000):
        money-=1
if __name__=='__main__':
    money=0
    t1 = threading.Thread(target=add)
    t2 = threading.Thread(target=reduce)
    t1.start()
    t2.start()
    #等待所有子线程执行结束
    t1.join()
    t2.join()
    print("最终金额为:%s" % (money))

Python在主线程中创建一个子线程 python 创建一个线程_多线程_10


分析:理论上money的值应该为0,但是,由于线程的调度是由操作系统决定的,当t1、t2交替执行时,只要循环次数足够多,money的结果就不一定是0了

原因:高级语言的一条语句在CPU执行时是若干条语句,即使一个简单计算money+=1 也是分为两步去完成的,先计算 money+1,将结果存入临时变量,再将临时变量赋值给money,而两个线程都拥有各自的临时变量,当t1与t2交替执行的时候,就会导致把同一个对象给改的混乱

所以我们对其进行加锁:线程锁lock

import threading

def add(lock):
    # 2.操作变量之前进行加锁
    lock.acquire()
    global money
    for i in range(1000000):
        money += 1
    # 3.操作变量完成后进行解锁
    lock.release()

def reduce(lock):
    # 2.操作变量之前进行加锁
    lock.acquire()
    global  money
    for i in range(1000000):
        money -= 1
    # 3.操作变量完成后进行解锁
    lock.release()

if __name__ == '__main__':
    money = 0
    # 1. 实例化一个锁对象
    lock = threading.Lock()
    t1 = threading.Thread(target=add, args=(lock, ))
    t2 = threading.Thread(target=reduce, args=(lock, ))
    t1.start()
    t2.start()
    # 等待所有子线程执行结束
    t1.join()
    t2.join()
    print("最终金额为:%s" %(money))

Python在主线程中创建一个子线程 python 创建一个线程_实例化_11


线程锁的本质:对同一对象进行操作时,一个线程在进行前加把锁,让其他线程不能对其进行操作,执行完成后,再进行解锁释放

七 Python中的GIL全局解释器锁

  • GIL(全局解释器锁)
  • python解释器默认每次只允许一个线程执行
    执行过程:
    1). 设置GIL
    2). 切换到线程去运行对应的任务;
    3). 运行
    -执行完了
    -time.sleep()
    -获取其他信息才能继续执行, eg: 从网络上获取网页信息等;
    4). 把线程设置为睡眠状态
    5). 解锁GIL
    6). 再次重复执行上述内容
    方法的选择:
    Python并不支持真正意义上的多线程。Python中提供了多线程包,但是如果你想通过多线程提高代码的速度,使用多线程包并不是个好主意。
    Python中有一个被称为Global Interpreter Lock(GIL)的东西,它会确保任何时候你的多个线程中,只有一个被执行。线程的执行速度非常之快,会让你误以为线程是并行执行的,但是实际上都是轮流执行。
    经过GIL这一道关卡处理,会增加执行的开销。这意味着,如果你想提高代码的运行速度,使用threading包并不是一个很好的方法。
import threading
from mytimeit import timeit
def job(l):
    sum(l)

@timeit
def use_thread():
    li = range(1,10000)
    for i in range(5):
        t = threading.Thread(target=job, args=(li, ))
        t.start()
@timeit
def use_no_thread():
    li = range(1, 10000)
    for i in range(5):
        job(li)

if __name__ == "__main__":
    use_thread()
    use_no_thread()

Python在主线程中创建一个子线程 python 创建一个线程_子线程_12


mytime模块中的timeit装饰器:

import time

def timeit(f):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        res = f(*args, **kwargs)
        end_time = time.time()
        print("%s运行时间为%s" %(f.__name__, end_time-start_time))
        return  res
    return  wrapper

八 队列与多线程

1).理论上多线程执行任务, 会产生一些数据, 为其他程序执行作铺垫;
2). 多线程是不能返回任务执行结果的, 因此需要一个容器来存储多线程产生的数据
3). 这个容器如何选择? list(栈, 队列), tuple(x), set(x), dict(x), 此处选择队列来实现

import threading
from collections import Iterable

from mytimeit import timeit
from queue import  Queue

def job(l, queue):
    # 将任务的结果存储到队列中;
    queue.put(sum(l))
@timeit
def use_thread():
    # 实例化一个队列, 用来存储每个线程执行的结果;
    q = Queue()
    # 入队
    # q.put(1)
    li = [[1,2,3,4,5], [2,3,4,5,6], [2,3,4,5,6,7,8], [2,3,4,5,6]]
    threads = []
    for i in li:
        t = threading.Thread(target=job, args=(i, q))
        threads.append(t)
        t.start()

    # join方法等待所有子线程执行结束
    [thread.join() for thread in threads]
    # 从队列里面拿出所有的运行结果
    result = [q.get() for _ in li]
    print(result)
    print(isinstance(q, Iterable))

if __name__ == "__main__":
    use_thread()

Python在主线程中创建一个子线程 python 创建一个线程_子线程_13

九 线程池

from concurrent.futures import ThreadPoolExecutor
import time
#需要执行的任务
def job():
    print('This is a job')
    return 'hello'
if __name__=='__main__':
    #实例化对象,线程池包含10个线程来处理任务
    pool=ThreadPoolExecutor(max_workers=10)
    #往线程池里面扔需要执行的任务, 返回一个对象,( _base.Future实例化出来的)
    f1=pool.submit(job)
    f2=pool.submit(job)
    #判断任务是否执行结束
    print(f1.done())
    time.sleep(1)
    print(f2.done())
    #获取任务执行的结果
    print(f1.result())
    print(f2.result())

Python在主线程中创建一个子线程 python 创建一个线程_子线程_14

十 线程池中的submit提交与map方法

from urllib.error import HTTPError
from urllib.request import urlopen
from concurrent.futures import ThreadPoolExecutor
from concurrent.futures import as_completed
import time
URLS = ['http://httpbin.org', 'http://example.com/',
        'https://api.github.com/'] * 50
def get_page(url, timeout=3):
    try:
        content = urlopen(url).read()
        return {'url':url, 'len':len(content)}
    except HTTPError as e:
        return {'url':url, 'len':0}

# 方法1: submit提交任务
start_time = time.time()
pool = ThreadPoolExecutor(max_workers=20)
futuresObj = [pool.submit(get_page, url) for url in URLS]

for future in futuresObj:
    print(future.result())

print("执行时间:%s" %(time.time()-start_time))

Python在主线程中创建一个子线程 python 创建一个线程_Python在主线程中创建一个子线程_15


Python在主线程中创建一个子线程 python 创建一个线程_Python在主线程中创建一个子线程_16


综上map处理的稍快一些,考虑到map更加简洁,推荐使用map处理。