【操作系统】多线程编程

1.线程是什么

线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
在这里插入图片描述
每个进程至少有一个线程,即进程本身,称为主线程。进程可以启动多个线程。操作系统像并行“进程”一样执行这些线程。
在这里插入图片描述
线程的分类
有两种不同的线程:

  • 内核线程
  • 用户空间线程或用户线程

内核线程是操作系统的一部分,而内核中没有实现用户空间线程。

2.进程和线程的区别

进程是资源分配的最小单位,线程是程序执行的最小单位(CPU调度的最小单位)。
进程有自己的独立地址空间。线程是共享进程中的数据的,使用相同的地址空间.
进程之间的通信需要以通信的方式(IPC)进行。线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,难点:处理好同步与互斥。

3.多线程编程的实现方式

3.1 实例化对象

python的thread模块是⽐较底层的模块,python的threading 模块是对thread做了⼀些包装的,可以更加⽅便的被使⽤。

import time
import threading
def task():
    """当前要执行的任务"""
    print("听音乐........")
    time.sleep(1)

if __name__ == '__main__':
    start_time = time.time()
    threads = []
    for  count in range(5):
        t = threading.Thread(target=task)
        # 让线程开始执行任务
        t.start()
        threads.append(t)

    # 等待所有的子线程执行结束, 再执行主线程;
    [thread.join() for thread in threads]
    end_time = time.time()
    print(end_time-start_time)

多线程程序的执⾏顺序是不确定的
当执⾏到sleep语句时,线程将被阻塞(Blocked),到sleep结束后,线程进⼊就绪(Runnable)状态,等待调度。⽽线程调度将⾃⾏选择⼀个线程执⾏。
代码中只能保证每个线程都运⾏完整个run函数,但是线程的启动顺序、 run函数中每次循环的执⾏顺序都不能确定。
在这里插入图片描述

3.1.1 基于多线程的IP归属地查询

import requests,json
import sqlalchemy
from sqlalchemy import Column,Integer,String
from sqlalchemy.orm import Session,sessionmaker
from sqlalchemy.ext.declarative import declarative_base
import pymysql
from threading import Thread
pymysql.install_as_MySQLdb()

content = []
#创建数据库的连接
engine = sqlalchemy.create_engine('mysql://root:westos@localhost/ip',encoding='utf-8')
#创建缓存对象
Session = sessionmaker(bind=engine)
session = Session()
#声明基类
Base = declarative_base()

#创建数据库表
class IpQuery(Base):
    __tablename__ = 'ips'
    id = Column(Integer,primary_key=True,autoincrement=True)
    ip = Column(String(20),nullable=False)
    city = Column(String(20))
    country = Column(String(20))

    def __repr__(self):
        return '%s,%s,%s,%s\n' %(self.id,self.ip,self.city,self.country)


#获取ip归属地的函数
def task(ip):
    """获取指定IP的所在城市和国家,并存储到数据库中"""
    #获取网址的返回内容
    url = 'http://ip-api.com/json/%s' %(ip)
    try:
        response = requests.get(url)
    except Exception as e:
        print('网页获取错误',e)
    else:
        #默认返回的是字符串
        """
        {"as":"AS174 Cogent Communications","city":"Beijing","country":"China","countryCode":"CN","isp":"China Unicom Shandong Province network","lat":39.9042,"lon":116.407,"org":"NanJing XinFeng Information Technologies, Inc.","query":"114.114.114.114","region":"BJ","regionName":"Beijing","status":"success","timezone":"Asia/Shanghai","zip":""}
        """
        contentPage = response.text
        #将页面的json字符串转换成便于处理的字典
        data_dict = json.loads(contentPage)
        #获取对应的城市和国家
        city = data_dict.get('city','null') #None对象无法直接存储到数据库中
        country = data_dict.get('country','null')
        content.append([ip,city,country])


if __name__ == '__main__':
    ipList = []
    threads = []
    for i in range(255):
        ip = '1.1.1.%s' %(str(i+1))
        thread = Thread(target=task,args=(ip,))
        thread.start()
        threads.append(thread)
    [thread.join() for thread in threads]

    Base.metadata.create_all(engine)

    for i in content:
        p = IpQuery(ip=i[0],city=i[1],country=i[2])
        ipList.append(p)
    session.add_all(ipList)
    session.commit()


    def __str__(self):
        return '%s,%s,%s,%s\n' %(self.id,self.ip,self.city,self.country)

    Base.metadata.create_all(engine)

    threads = []
    for item in range(10):
        ip = '192.168.1.'+str(item+1)
        task(ip)

        #多线程执行任务
        thread = Thread(target=task,args=(ip,))
        thread.start()
        threads.append(thread)

    [thread.join() for thread in threads]
    print('任务结束')
    print(session.query(IpQuery).all)

3.2 创建子类

3.2.1 基于多线程的批量主机存活探测

如果要在本地网络中确定哪些地址处于活动状态或哪些计算机处于活动状态,则可以使用此脚本。我们将依次ping地址, 每次都要等几秒钟才能返回值。这可以在Python中编程,在IP地址的地址范围内有一个for循环和一个os.system(“ping -c -w”+ ip)。
但是,没有线程的解决方案效率非常低,因为脚本必须等待每次ping。

from threading import  Thread
class GetHostAliveThread(Thread):
    """
    创建子线程, 执行的任务:判断指定的IP是否存活
    """
    def __init__(self, ip):
        super(GetHostAliveThread, self).__init__()
        self.ip = ip
    def run(self):
        # # 重写run方法: 判断指定的IP是否存活
        # """
        # >>> # os.system()  返回值如果为0, 代表命令正确执行,没有报错; 如果不为0, 执行报错;
        # ...
        # >>> os.system('ping -c1 -w1 192.168.22.49 &> /dev/null')
        # 0
        # >>> os.system('ping -c1 -w1 192.168.22.1 &> /dev/null')
        # 256
        # """
        import os
        # 需要执行的shell命令
        cmd = 'ping -c1 -w1 %s &> /dev/null' %(self.ip)
        result = os.system(cmd)
        #  返回值如果为0, 代表命令正确执行,没有报错; 如果不为0, 执行报错;
        if result != 0:
            print("%s主机没有ping通" %(self.ip))
if __name__ == '__main__':
    print("打印192.168.22.0网段没有使用的IP地址".center(50, '*'))
    for i in range(1, 255):
        ip = '192.168.22.' + str(i)
        thread = GetHostAliveThread(ip)
        thread.start()

4.共享全局变量

优点: 在⼀个进程内的所有线程共享全局变量,能够在不使⽤其他⽅式的前提 下完成多线程之间的数据共享(这点要⽐多进程要好)

缺点: 线程是对全局变量随意遂改可能造成多线程之间对全局变量 的混乱(即线程⾮安全)

money = 0
def add():
    for i in range(1000000):
        global  money
        money +=1
        
def reduce():
    for i in range(1000000):
        global money
        money -= 1


if __name__ == '__main__':
    from threading import Thread,Lock
    lock = Lock()
    t1 = Thread(target=add)
    t2 = Thread(target=reduce)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(money)

在这个代码中,全局变量money一开始为0,实例化了两个线程类,其中t1对money进行+1操作,t2对money进行-1操作。按理来说,加一减一应该是0,但是结果并非如此。(循环次数过少,不会出现线程非安全问题)多个线程在对同一个数据进行修改时,可能会出现不可预料的情况。

4.1 GIL是什么

如何解决 线程非安全的问题呢? 加锁!在一个线程对数据进行操作时,禁止其他的线程对此数据操作。
GIL(global interpreter lock)全局解释器锁: python解释器中任意时刻都只有一个线程在执行;
Python代码的执行由Python 虚拟机(也叫解释器主循环,CPython版本)来控制,Python 在设计之初就考虑到要在解释器的主循环中,同时只有一个线程在执行,即在任意时刻,只有一个线程在解释器中运行。对Python 虚拟机的访问由全局解释锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
想要实现多线程,可以用Jpython解释器,或者多进程+协程的方式实现。

4.2 线程同步

线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作.

4.3 如何实现线程锁

  1. 实例化一个锁对象lock = threading.Lock()
  2. 操作变量之前,进行加锁。lock.acquire()
  3. 操作变量之后,进行解锁。lock.release()
money = 0
def add():
    for i in range(1000000):
        global  money
        lock.acquire()
        money +=1
        lock.release()

def reduce():
    for i in range(1000000):
        global money
        lock.acquire()
        money -= 1
        lock.release()

if __name__ == '__main__':
    from threading import Thread,Lock
    lock = Lock()
    t1 = Thread(target=add)
    t2 = Thread(target=reduce)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(money)

再看输出的结果,一定是0

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
使用 JavaScript 编写的记忆游戏(附源代码)   项目:JavaScript 记忆游戏(附源代码) 记忆检查游戏是一个使用 HTML5、CSS 和 JavaScript 开发的简单项目。这个游戏是关于测试你的短期 记忆技能。玩这个游戏 时,一系列图像会出现在一个盒子形状的区域中 。玩家必须找到两个相同的图像并单击它们以使它们消失。 如何运行游戏? 记忆游戏项目仅包含 HTML、CSS 和 JavaScript。谈到此游戏的功能,用户必须单击两个相同的图像才能使它们消失。 点击卡片或按下键盘键,通过 2 乘 2 旋转来重建鸟儿对,并发现隐藏在下面的图像! 如果翻开的牌面相同(一对),您就赢了,并且该对牌将从游戏中消失! 否则,卡片会自动翻面朝下,您需要重新尝试! 该游戏包含大量的 javascript 以确保游戏正常运行。 如何运行该项目? 要运行此游戏,您不需要任何类型的本地服务器,但需要浏览器。我们建议您使用现代浏览器,如 Google Chrome 和 Mozilla Firefox, 以获得更好、更优化的游戏体验。要玩游戏,首先,通过单击 memorygame-index.html 文件在浏览器中打开游戏。 演示: 该项目为国外大神项目,可以作为毕业设计的项目,也可以作为大作业项目,不用担心代码重复,设计重复等,如果需要对项目进行修改,需要具备一定基础知识。 注意:如果装有360等杀毒软件,可能会出现误报的情况,源码本身并无病毒,使用源码时可以关闭360,或者添加信任。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值