线程介绍

多任务的概念
什么叫“多任务”呢?简单地说,就是操作系统可以同时运行多个任务。打个比方,你一边看电影,一边聊QQ,一边在用Word赶作业,这就是多任务,这时至少同时有3个任务正在运行。
单核CPU如何执行多任务?
多核CPU如何执行多任务?
真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。
注意:
并发:指的是任务数多余cpu核数,通过操作系统的各种任务调度算法,实现用多个任务“一起”执行(实际上总有一些任务不在执行,因为切换任务的速度相当快,看上去一起执行而已)
并行:指的是任务数小于等于cpu核数,即任务真的是一起执行的

线程

python的thread模块是比较底层的模块,python的threading模块是对thread做了一些包装的,可以更加方便的被使用

  1. 使用threading模块

单线程执行

#coding=utf-8 
import time 
def saySorry():
   for i in range(5):
	    print("亲爱的,我错了,我能吃饭了吗?")
	    time.sleep(1) 
def do(): 
	 for i in range(5):
		  print("亲爱的,我错了,我给你按摩") 
		  time.sleep(1) 
if __name__ == "__main__": 
	saySorry() 
	saydo()

多线程执行

#coding=utf-8 
import time 
import threading
def saySorry():
   for i in range(5):
	    print("亲爱的,我错了,我能吃饭了吗?")
	    time.sleep(1) 
def do(): 
	 for i in range(5):
		  print("亲爱的,我错了,我给你按摩") 
		  time.sleep(1) 
if __name__ == "__main__": 
	td1 = threading.Thread(target=saySorry)
	td1.start() #启动线程,即让线程开始执行
	td2 = threading.Thread(target=saySorry)
	td2.start() #启动线程,即让线程开始执行

threading.Thread参数介绍

参数

作用

target

线程执行的函数

name

线程名字

args

执行函数需要传递的参数

kwargs

执行函数需要传递的参数

daemon

如果某个子线程的daemon属性为True,主线程运行结束时不对这个子线程进行检查而直接退出,同时所有daemon值为True的子线程将随主线程一起结束,而不论是否运行完成。

daemon

属性daemon的值默认为False,如果需要修改,必须在调用start()方法启动线程之前进行设置

线程的生命周期

java使用线程池同时执行三个任务_线程池


首先使用new Thread()的方法新建一个线程,在线程创建完成之后,线程就进入了就绪(Runnable)状态,此时创建出来的线程进入抢占CPU资源的状态,当线程抢到了CPU的执行权之后,线程就进入了运行状态(Running),当该线程的任务执行完成之后或者是非常态的调用的stop()方法之后,线程就进入了死亡状态。而我们在图解中可以看出,线程还具有一个则色的过程,这是怎么回事呢?当面对以下几种情况的时候,容易造成线程阻塞,第一种,当线程主动调用了sleep()方法时,线程会进入则阻塞状态,除此之外,当线程中主动调用了阻塞时的IO方法时,这个方法有一个返回参数,当参数返回之前,线程也会进入阻塞状态,还有一种情况,当线程进入正在等待某个通知时,会进入阻塞状态。那么,为什么会有阻塞状态出现呢?我们都知道,CPU的资源是十分宝贵的,所以,当线程正在进行某种不确定时长的任务时,Java就会收回CPU的执行权,从而合理应用CPU的资源。我们根据图可以看出,线程在阻塞过程结束之后,会重新进入就绪状态,重新抢夺CPU资源。这时候,我们可能会产生一个疑问,如何跳出阻塞过程呢?又以上几种可能造成线程阻塞的情况来看,都是存在一个时间限制的,当sleep()方法的睡眠时长过去后,线程就自动跳出了阻塞状态,第二种则是在返回了一个参数之后,在获取到了等待的通知时,就自动跳出了线程的阻塞过程。

单线程和多线程概念

单线程,顾名思义即是只有一个线程在执行任务,这种情况在我们日常的工作学习中很少遇到,所以我们只是简单做一下了解

多线程,创建多个线程同时执行任务,这种方式在我们的日常生活中比较常见。但是,在多线程的使用过程中,还有许多需要我们了解的概念。比如,在理解上并行和并发的区别,以及在实际应用的过程中多线程的安全问题,对此,我们需要进行详细的了解。

并行和并发:在我们看来,都是可以同时执行多种任务,那么,到底他们二者有什么区别呢?

并发:从宏观方面来说,并发就是同时进行多种时间,实际上,这几种时间,并不是同时进行的,而是交替进行的,而由于CPU的运算速度非常的快,会造成我们的一种错觉,就是在同一时间内进行了多种事情

并行:则是真正意义上的同时进行多种事情。这种只可以在多核CPU的基础上完成。
还有就是多线程的安全问题?为什么会造成多线程的安全问题呢?我们可以想象一下,如果多个线程同时执行一个任务,意味着他们共享同一种资源,由于线程CPU的资源不一定可以被谁抢占到,这是,第一条线程先抢占到CPU资源,他刚刚进行了第一次操作,而此时第二条线程抢占到了CPU的资源,共享资源还来不及发生变化,就同时有两个线程使用了同一条资源,会造成数据不一致性,导致线程执行错误发生。 有造成问题的原因我们可以看出,这个问题主要的矛盾在于,CPU的使用权抢占和资源的共享发生了冲突,解决时,我们只需要让一条线程占用了CPU的资源时,阻止第二条线程同时抢占CPU的执行权,在代码中,我们只需要在方法中使用同步代码块即可。在这里,同步代码块不多进行赘述。

互斥锁(重点)

当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制
线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。
互斥锁为资源引入一个状态:锁定/非锁定
某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
threading模块中定义了Lock类,可以方便的处理锁定:

# 创建锁 mutex = threading.Lock() 
# 锁定 mutex.acquire() 
# 释放 mutex.release()

注意:

  • 如果这个锁之前是没有上锁的,那么acquire不会堵塞
  • 如果在调用acquire对这个锁上锁之前 它已经被 其他线程上了锁,那么此时acquire会堵塞,直到这个锁被解锁为止
    使用互斥锁完成2个线程对同一个全局变量各加100万次的操作:
sum =1
# def run1(num,**kwargs):
#     print(kwargs)
#     lock.acquire()
#     global sum
#     for i in range(num):
#         # print(i,threading.currentThread().name)
#         data.append(i)
#         sum+=1
#     lock.release()
# def run2(num):
#     lock.acquire()
#     global sum
#     for i in range(num):
#         # print(i,threading.currentThread().name)
#         data.append(i)
#         sum += 1
#     lock.release()
# if __name__ == '__main__':
#     print('开始执行主线程',threading.currentThread().name)#默认的主线程
#     # 创建线程
#     # target执行的函数
#     # name线程的名称
#     # args,kwargs都是传参.
#     # daemonm默认为false,主线程结束不影响子线程执行

加入互斥锁后,其结果与预期相符。
上锁解锁过程
当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。
每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“阻塞”,直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入“unlocked”状态。
线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。
总结
锁的好处:

  • 确保了某段关键代码只能由一个线程从头到尾完整地执行

锁的坏处:

  • 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
  • 由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁

死锁问题

在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。

线程池

使用python3.2之后为我们封装的线程池
线程池爬虫案例:

from lxml.html import etree
import requests,re,threading
from concurrent.futures import ThreadPoolExecutor

class CollegateRank(object):

    def get_page_data(self, url):
        response = self.send_request(url=url)
        if response:
            # print(response)
            with open('page.html', 'w', encoding='gbk') as file:
                file.write(response)
            self.parse_page_data(response)

        nums = re.findall('\d', url)
        nums = int(nums[0]) + 1
        if nums < 108:
            next_url = 'http://college.gaokao.com/schlist/p%d/' % nums
            self.get_page_data(next_url)
            self.show_data()

    def parse_page_data(self, response):
        # 使用xpath解析数据
        etree_xpath = etree.HTML(response)
        ranks = etree_xpath.xpath('//div[@class="scores_List"]/dl')
        # print(ranks)
        pool = ThreadPoolExecutor(10)#创建线程池
        for dl in ranks:
            school_info = {}
            school_info['url'] = self.extract_first(dl.xpath('./dt/a[1]/@href'))
            school_info['icon'] = self.extract_first(dl.xpath('./dt/a[1]/img/@src'))
            school_info['name'] = self.extract_first(dl.xpath('./dt/strong/a/text()'))
            school_info['adress'] = self.extract_first(dl.xpath('./dd/ul/li[1]/text()'))
            school_info['tese'] = '、'.join(dl.xpath('./dd/ul/li[2]/span/text()'))
            school_info['type'] = self.extract_first(dl.xpath('./dd/ul/li[3]/text()'))
            school_info['belong'] = self.extract_first(dl.xpath('./dd/ul/li[4]/text()'))
            school_info['level'] = self.extract_first(dl.xpath('./dd/ul/li[5]/text()'))
            school_info['weburl'] = self.extract_first(dl.xpath('./dd/ul/li[6]/text()'))
            thread = threading.Thread(target=self.get_school, args=(school_info['url'],),kwargs={'name':school_info['name']})
            thread.start()
            print(school_info)
            result = pool.submit(self.send_request,school_info['url'])
            result.add_done_callback(self.show_data)
        pool.shutdown()
    def extract_first(self, data=None, defalut=None):
        if len(data) > 0:
            return data[0]
        return defalut

    def send_request(self, url, headers=None):
        headers = headers if headers else {
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36'}
        response = requests.get(url=url, headers=headers)
        if response.status_code == 200:
            return response.text

    def show_data(self,future):
        response = future.result()
        if response:
            print('学院详情')
            etrees = etree.HTML(response)
            data = etrees.xpath('//div[@class="college_msg bk"]/dl')
            for i in data:
                school_data = {}
                school_data['icon'] =self.extract_first(i.xpath('./dt[@class="left"]/img/@src'))
                school_data['tese'] = '、'.join(i.xpath('./dd/ul[@class="left basic_infor"]/li[1]/span/text()'))
                school_data['membership'] = self.extract_first(i.xpath('./dd/ul[@class="left basic_infor"]/li[2]/text()'))
                school_data['address'] = self.extract_first(i.xpath('./dd/ul[@class="left basic_infor"]/li[3]/text()'))
                school_data['peroel'] = self.extract_first(i.xpath('./dd/ul[@class="left basic_infor"]/li[4]/text()'))

                print(school_data)
    def get_school(self,url,**kwargs):
        response = self.send_request(url=url)
        if response:
            with open(kwargs['name']+'.html','w') as file:
                file.write(response)
if __name__ == '__main__':
    url = 'http://college.gaokao.com/schlist/p1/'
    obj = CollegateRank()
    obj.get_page_data(url)