资料来源:https://www.cnblogs.com/alex3714/articles/5230609.html

http://python.jobbole.com/86406/

https://www.cnblogs.com/wupeiqi/articles/5040827.html

https://www.cnblogs.com/tkqasn/p/5700281.html

在此感谢前辈们的指导,以下均为纯手打

一,程序,进程,线程概念的区分

在计算机中,程序不能单独运行,必须把它分配到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。

一句话总结:程序是指令的集合,它是程序的静态描述文本;进程确是程序的一次执行活动,是动态概念

那么,线程是什么呢,它与进程有什么关系呢,我们看几个例子:

线程是执行上下文,它是CPU执行指令流所需的全部信息。

1,假设你正在读一本书,现在你想休息一下,但你希望能够回来,从你停下来的确切地点恢复阅读。一个实现的方法就是记下页码,行数和字数。所以你阅读书的执行上下文是这3个数字。

2,如果你有一个室友,而且她使用相同的技巧,她可以在你不使用的时候拿书,然后从她停止阅读的地方继续阅读。然后你可以收回它,从你原来的地方恢复它。

因此执行上下文(因此线程)由CPU寄存器的值组成。

综上:线程不同于进程。线程是执行的上下文,而进程是与计算相关联的一堆资源。一个进程可以有一个或多个线程(资源当中肯定不止一个上下文)。

关于进程与线程还有几个重要的区别

1,与进程相关的资源包括内存页(进程中的所有线程对内存具有相同的视图)、文件描述符(例如,打开套接字)和安全凭据(例如启动进程的用户ID)。

2,很容易创建新线程;新进程需要重复父进程。

3,线程可以对相同进程的线程进行相当的控制;进程只能对子进程进行控制。

4,对主线程的更改(取消、优先级更改等)可能影响进程的其他线程的行为;对父进程的更改不会影响子进程。

二:有了进程,为什么需要线程(拓展)

1,进程只能在一个时间内干一件事,但如果想同时干两件或多件事,进程就无能为力了。

2,进程在执行过程中,如果发生阻塞,那么进程就会挂起,无法执行程序。

三,道路从线程开始:

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

threading 模块提供的常量:

  threading.TIMEOUT_MAX 设置threading全局超时时间。

线程的方法:

  • start            线程准备就绪,等待CPU调度

  • setName      为线程设置名称

  • getName      获取线程名称

  • setDaemon   设置为后台线程或前台线程(默认)
                       如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止
                        如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止

  • join              逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义

  • run              线程被cpu调度后自动执行线程对象的run方法

接下来,围绕这几个方法,和线程的两种调用方式进行讲解

1,直接调用:

import threading
import time
def sayhi(num):
   print("running on number:%s"%num)
   time.sleep(3)
if __name__=='__main__':
   t1=threading.Thread(target=sayhi,args=(1,))#线程实例,逗号必须加
   t2 = threading.Thread(target=sayhi, args=(2,))  # 线程实例
   t1.start()#启动线程
   t2.start()#启动另一个线程
   print(t1.getName())

   print(t2.getName())

# 构造方法:
# Thread(group=None, target=None, name=None, args=(), kwargs={})
#
#   group: 线程组,目前还没有实现,库引用中提示必须是None;
#   target: 要执行的方法;
#   name: 线程名;

#   args / kwargs: 要传入方法的参数。

再来看一个间接调用(继承式调用):

import threading
import time
class MyThread(threading.Thread):
   def __init__(self, num):
       threading.Thread.__init__(self)
       self.num = num
   def run(self):  # 定义每个线程要运行的函数

       print("running on number:%s" % self.num)

       time.sleep(3)
if __name__ == '__main__':
   t1 = MyThread(1)
   t2 = MyThread(2)
   t1.start()
   t2.start()

接下来对这两个调用进行深究

一下子开五十个进程,看看时间多少

import threading
import time
def run(n):
   print("task",n)
   time.sleep(2)
   print("task done", n)
start_time=time.time()
for i in range(50):
   t1=threading.Thread(target=run,args=("t-%s"%i,))
   t1.start()

print("cost:",time.time()-start_time)

观察它所消耗的时间是

摘取部分结果

task t-46

task t-47

task t-48

task t-49

------------all threads has finished

cost: 0.0070035457611083984

task done t-2

task done t-1

task done t-0

task done t-8

为什么会出现这种情况呢,是因为主线程与子线程无关,不能在主线程中测子线程,因为主线程会执行自己的,,这里可能说的有点抽象,emmmmn,其实我也是在把这遍代码,码完后,才有些懂得。

主线程可以看成是:

import threading
import time
def run(n):
   print("task",n

start_time=time.time()
for i in range(50):
   t1=threading.Thread(target=run,args=("t-%s"%i,))
   t1.start()

print("cost:",time.time()-start_time)

这段代码集合(把run代码中的sleep去掉了),他只会执行自己的,不会等子线程的结束,所以将task打完后就把时间打印出来

那么如何让主线程等待呢:

import threading
import time
def run(n):
   print("task",n)
   time.sleep(2)
   print("task done",threading.current_thread())#返回到当前线程的对象
start_time=time.time()
t_objs=[]
for i in range(50):
   t1=threading.Thread(target=run,args=("t-%s"%i,))
   t1.start()
   t_objs.append(t1)
for t in t_objs:
   t.join()#结束
print("------------all threads has finished",threading.current_thread())

print("cost:",time.time()-start_time)#主线程与子线程无关,所以不能在主线程中测子线程

部分结果展示:

task done

task done

task done

task done

task done

task done

task done

task done

task done

task done

task done

task done

task done

------------all threads has finished <_MainThread(MainThread, started 10780)>

cost: 2.0096378326416016

此时可以在主线程中测子线程了

注意,为什么多线程会称之为多线程,因为会用join方法,有的哥们儿这样写

import threading
import time
def run(n):
   print("task",n)
   time.sleep(2)
   print("task done",threading.current_thread())#返回到当前线程的对象
start_time=time.time()
t_objs=[]
for i in range(50):
   t1=threading.Thread(target=run,args=("t-%s"%i,))
   t1.start()
   t_objs.append(t1)
   t1.join()#结束
print("------------all threads has finished",threading.current_thread())
print("cost:",time.time()-start_time)#主线程与子线程无关,所以不能在主线程中测子线程

他就变成了顺序的线程了,造成线程的阻塞了,应该竭力避免。

好了,我们再来看看守护(后台)线程的实例:

setDaemon   设置为后台线程或前台线程(默认)
                   如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止

                    如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止

import threading
import time
def run(n):
   print("task",n)
   time.sleep(2)
   print("task done",threading.current_thread())#返回到当前线程的对象
start_time=time.time()
t_objs=[]
for i in range(50):
   t1=threading.Thread(target=run,args=("t-%s"%i,))
   t1.setDaemon(True)  # 把线程转变成守护线程,不管分线程,主进程结束了,直接结束,不等分线程
   t1.start()
   t_objs.append(t1)

# for t in t_objs:
#     t.join()#结束
print("------------all threads has finished",threading.current_thread())
print("cost:",time.time()-start_time)#主线程与子线程无关,所以不能在主线程中测子线程

这个案例比前面几个案例修改了什么了。没错,当主线程结束的时候,守护线程也会跟着结束

t1.setDaemon(True)  # 把线程转变成守护线程,不管分线程,主进程结束了,直接结束,不等分线程t1.start()

这个是固定格式,改变位置容易出错

以上是稍微简单的些的概念,接下来看第二部分

线程锁的引出:

一个进程可以启动多个进程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问一份数据,此时,如果有两个线程要修改同一份数据,会出现什么状况?

会导致数据出错,这里,我摘取前辈们的一部分解释,因为相比于2.7版本,3中做了许多改进,前辈们的问题已经不会出现,目测是python源代码中加了内置锁。

import  time
import  threading
 
def  addNum():
     global  num  #在每个线程中都获取这个全局变量
     print ( '--get num:' ,num )
     time.sleep( 1 )
     num   - = 1  #对此公共变量进行-1操作
 
num  =  100   #设定一个共享变量
thread_list  =  []
for  in  range ( 100 ):
     =  threading.Thread(target = addNum)
     t.start()
     thread_list.append(t)
 
for  in  thread_list:  #等待所有线程执行完毕
     t.join()
 
 
print ( 'final num:' , num )

正常来讲,这个num结果应该是0, 但在python 2.7上多运行几次,会发现,最后打印出来的num结果不总是0,为什么每次运行的结果不一样呢? 哈,很简单,假设你有A,B两个线程,此时都 要对num 进行减1操作, 由于2个线程是并发同时运行的,所以2个线程很有可能同时拿走了num=100这个初始变量交给cpu去运算,当A线程去处完的结果是99,但此时B线程运算完的结果也是99,两个线程同时CPU运算的结果再赋值给num变量后,结果就都是99。那怎么办呢? 很简单,每个线程在要修改公共数据时,为了避免自己在还没改完的时候别人也来修改此数据,可以给这个数据加一把锁, 这样其它线程想修改此数据时就必须等待你修改完毕并把锁释放掉后才能再访问此数据。 

上面是我复制粘贴,原话,这里就不作解释了

我们来看一下怎样避免前辈们的情况

import time
import threading
def addNum():
   global num#在每个线程中都获得这个全局变量
   print('--get num:',num)
   time.sleep(1)
   lock.acquire()#修改数据前加锁
   num+=1
   lock.release()  # 修改后释放
   print('--get num', num)
num=0
thread_list=[]
lock = threading.Lock() #生成全局锁
for i in range(100):
   t=threading.Thread(target=addNum)
   t.start()
   thread_list.append(t)
for t in thread_list:
   t.join()
print('final num',num)

这是关于线程锁的一个解释,我们再来看一看关于线程锁的另一个解释

递归锁(对不起,各位,我的递归锁部分本来花了两个小时准备的,但是,因为我是编辑文章,所以没保存到草稿中,现在只能把重要部分讲给大家)

import threading, time

def run1():
   print("grab the first part data")
   lock.acquire()
   global num
   num += 1
   lock.release()
   return num
def run2():
   print("grab the second part data")
   lock.acquire()
   global num2
   num2 += 1
   lock.release()
   return num2
def run3():
   lock.acquire()
   res = run1()#调用run1() run1()是一个方法
   print('--------between run1 and run2-----')
   res2 = run2()#执行run2
   lock.release()
   print(res, res2)
num, num2 = 0, 0
lock = threading.RLock()#有一把锁
for i in range(10):
   t = threading.Thread(target=run3)
   t.start()
while threading.active_count() != 1:#主线程也是一个线程
   print(threading.active_count())
else:
   print('----all threads done---')

   print(num, num2)

信号量

所锁只能一个对数据进行更改,但信号量可以多个

import threading,time
def run(n):
   semaphore.acquire()
   time.sleep(1)
   global num
   num+=1
   print("run the thread:%s\n"%n)
   print(num)
   semaphore.release()
if __name__=='__main__':

   num=0
   semaphore=threading.BoundedSemaphore(5)#最多允许5个线程同时进行
   for i in range(22):
       t=threading.Thread(target=run,args=(i,))
       t.start()
while threading.active_count()!=1:
   pass
else:

   print('---all threads done----')

以下分享三个案例

先来一个简单的实例

import threading
import time


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

   def run(self):
       print("I am %s,I will sleep ..." % self.name)
       self.singal.wait()
       print("I am %s, I awake..." % self.name)


if __name__ == "__main__":
   singal = threading.Event()
   for t in range(0, 3):
       thread = MyThread(singal)
       thread.start()

   print("main thread sleep 3 seconds... ")
   time.sleep(3)

   singal.set()

关于红绿灯的

import time
import threading
event=threading.Event()
def lighter():
   count=0
   event.set()  # 变绿灯
   while True:
       if count>5 and count <10:#这一步是改成红灯
           event.clear()
           print("\033[41;1mred light is on....\033[0m")
       elif count>10:
           event.set()#变绿灯
           count=0
       else:
           print("\033[42;1mgreen light is on....\033[0m")
       time.sleep(1)
       count+=1
def car(name):
   while True:
       if event.is_set(): #代表绿灯
           time.sleep(0.1)
           print("[%s] running..."%name)

       else:
           print("[%s] sees red light , waiting...." %name)
           event.wait()
           print("\033[34;1m[%s] green light is on, start going...\033[0m" %name)

light = threading.Thread(target=lighter,)
light.start()
car1 = threading.Thread(target=car,args=("Tesla",))
car1.start()

#此处还有一个案例没完成,一个线程与多个线程之间的通信

未完待续,最近几天会推出线程与爬虫结合的实例与模板