最近在学习多线程的时候,代码书写逻辑不对,导致线程总是执行重复的操作,,记录一下自己遇到的创建线程的几种方式.
线程(thread)是进程(process)中的一个实体,一个进程至少包含一个线程。比如,对于视频播放器,显示视频用一个线程,播放音频用另一个线程。如果我们把进程看成一个容器,则线程是此容器的工作单位。
进程和线程的区别主要有:
进程之间是相互独立的,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,但互不影响;而同一个进程的多个线程是内存共享的,所有变量都由所有线程共享;
由于进程间是独立的,因此一个进程的崩溃不会影响到其他进程;而线程是包含在进程之内的,线程的崩溃就会引发进程的崩溃,继而导致同一进程内的其他线程也崩溃.
在Python中,进行多线程的编程模块有两个: thread和threadding,,前者是低级模块,后者是高级模块,经常用的是高级模块,是对thread的封装,/
锁机制:
由于同一个进程之间的线程是内存共享的,所以当多个线程对同一个变量进行修改的时候,就会得到意想不到的结果。
先看一个简单例子:
# 多线程简单例子
from threading import Thread, current_thread
num = 0
def calc():
global num
print('thread %s is running...' % current_thread().name) # 输出线程名
for _ in range(100000):
num += 1
print('thread %s end.....' % current_thread().name)
if __name__ == '__main__':
print('thread %s is running...' % current_thread().name)
threads=[] #创建线程池
for i in range(5):
threads.append(Thread(target=calc))# 添加线程
threads[i].start()#启动线程
for i in range(5):
threads[i].join()
print('global num: %d' % num)
在上面的代码中,我们创建了 5 个线程,每个线程对全局变量 num 进行 10000 次的 加 1 操作,这里之所以要循环 10000 次,是为了延长单个线程的执行时间,使线程执行时能出现中断切换的情况。现在问题来了,当这 5 个线程执行完毕时,全局变量的值是多少呢?是 50000 吗?不一定是:
原因是 num + 1不是一个原子操作,也就是在执行的时候是被分成若干步执行了,
由于线程是交替运行的,线程在执行过程中可能会中断,导致其他线程读到一个脏值,
为了保证计算的准确性,我们就需要给 num += 1 这个操作加上 锁 。当某个线程开始执行这个操作时,由于该线程获得了锁,因此其他线程不能同时执行该操作,只能等待,直到锁被释放,这样就可以避免修改的冲突。创建一个锁可以通过 threading.Lock() 来实现,代码如下:
# 多线程简单例子
from threading import Thread, current_thread,Lock
num = 0
lock=Lock()
def calc():
global num
print('thread %s is running...' % current_thread().name) # 输出线程名
for _ in range(100000):
lock.acquire() #加锁
num += 1
lock.release()#解锁
print('thread %s end.....' % current_thread().name)
if __name__ == '__main__':
print('thread %s is running...' % current_thread().name)
threads=[] #创建线程池
for i in range(5):
threads.append(Thread(target=calc))# 添加线程
threads[i].start()#启动线程
for i in range(5):
threads[i].join()
print('global num: %d' % num)
1.采用创建指定进程数量来去处理项目,不采用传参和全局变量控制方式,
import time
from threading import Thread, current_thread, Lock
def add():
for i in range(1,255):
ip_h='192.168.181.'
ip=ip_h+str(i)
print(ip)
ips.append(ip)
if __name__ == '__main__':
start=time.time()
ips=[]
threads=[]
for i in range(5):
t=Thread(target=add,)
threads.append(t)
t.start()
for i in threads:
i.join()
end=time.time()
print("总时间"+str(start-end))
可以看到在这种情况下,线程会重复执行,里面会有重复项出现,但执行的时间变短,但经常这种方式,不是我们想要的,因为我们不需要重复,而是直接依次的向下处理.造成这种结果的原因是,线程之间没有锁机制,然后线程之间采用并行结构,所以当线程1在处理第一个的时候,后面的都不会影响,都是从1开始处理,.
2,创建的线程是与需要的参数相关,想当于创建了254个线程,去同时执行,且在没有加锁的状态下,
import time
from threading import Thread, current_thread, Lock
def add(i):
# for i in range(1,255):
ip_h='192.168.181.'
ip=ip_h+str(i)
print(ip)
ips.append(ip)
if __name__ == '__main__':
start=time.time()
ips=[]
threads=[]
for i in range(255):
t=Thread(target=add,args=(i,))
threads.append(t)
t.start()
for i in threads:
i.join()
end=time.time()
print("总时间"+str(start-end))
.而这种写法,是将线程创建和参数值关联起来,每一个线程每次只会拿到一个参数值进行处理,处理结束后,整个线程 也就结束了,所以不会有重复的出现,但这种方式,处理线程并发数与参数有关,且并发量过高,如果其中有局部变量的情况下,会每次修改掉这个局部变量.对于这种情况需要采用其他方式,如:Python 提供了 ThreadLocal 对象,它真正做到了线程之间的数据隔离.
3,采用全局变量方式,对线程处理数进行限制,这样,一个线程每一次只能拿到一个值,且这个值还是进过累加的,也就不会产生重复执行的情况.
不加锁:
#(2)创建指定线程,通过全局变量方式,控制线程不执行重复操作
import time
from threading import Thread, current_thread, Lock
def add():
global num #全局变量关键字
ip_h = '192.168.181.'
for i in range(51): # 这里的i是用来限制单个线程的所用到的循环次数,通过 处理项目总数/线程数 算出来的
# lock.acquire()
ip=ip_h+str(num) #这里是控制线程执行顺序,当下一次线程进来,也只能只能执行,下一次项目,不会再重复处理之前的值
num += 1
# lock.release()
print(ip)
ips.append(ip)
if __name__ == '__main__':
start=time.time()
lock=Lock()
num=1
ips=[]
threads=[]
for i in range(5):
t=Thread(target=add,)
threads.append(t)
t.start()
for i in threads:
i.join()
end=time.time()
print("总时间"+str(start-end))
加锁情况下:
可以看到时间上还是有一定差距的.