网络编程进阶:并发编程之多线程

2023-05-16

多线程:

在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程; 进程的作用就是隔离数据。

进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是CPU上的执行单位。(进程必须靠线程去执行)

线程就类似于一条流水线工作的过程;多线程(即多个控制线程)的概念是:在一个进程中存在多个线程,多个线程共享该进程的地址空间;

线程、进程区别: 

1. 同一个进程内的多个线程共享该进程内的地址资源;

2. 创建线程的开销远小于创建进程的开销(创建一个进程需要向操作系统申请新的地址空间)

 

开启线程的两种方式(跟开启进程相似):

方式一:


from threading import Thread
import time

def sayhi(name):
    print("hello %s"%name)
    time.sleep(1)
    print("bye %s"%name)

if __name__ == "__main__":
    t1 = Thread(target=sayhi,args=("neo",))
    t1.start()  # t1.start()也是给操作系统发信号,但是操作系统已经不需要开辟新的内存空间
    print("主线程")   #  站在执行的角度看,这个py文件是主线程;站在资源的角度看,这个是主进程

# 运行结果:
# hello neo    # 先打印的是新线程中的内容,因为开辟新线程t1.start()只需要给操作系统发信号,不需要开辟新的内存空间,所以新线程内的任务可以立马执行;所以说创建线程的开销远小于创建进程
# 主线程
# bye neo  

 方式二:


from threading import Thread
import time

class MyThread(Thread):
    def __init__(self,name):
        super().__init__()   # Thread子类的__init__()中,必须先写 super().__init__(),即先继承父类的__init__()再定义自己的逻辑
        self.name = name

    """
    If the subclass overrides the constructor, it must make sure to invoke the base class constructor (Thread.__init__()) 
    before doing anything else to the thread.
    """

    def run(self):
        print("hello %s"%self.name)
        time.sleep(1)
        print("bye %s"%self.name)

if __name__ == "__main__":
    t1 = MyThread("neo")
    t1.start()
    print("主线程")

# 运行结果:
# hello neo
# 主线程
# bye neo  

 查看当前进程的pid也可以利用multiprocessing模块下的 current_process().pid

 

Thread对象的其他属性或方法:


from threading import Thread,current_thread,active_count,enumerate
import time

def task():
    print("%s is running"%current_thread().getName())  # current_thread()是返回当前的线程变量; # getName()是返回线程名
    time.sleep(1)
    print("%s is done"%current_thread().getName())

if __name__ == "__main__":
    t = Thread(target=task,name="子线程1")  # 线程之间的地位是平等的,没有主、子之分; # name= 要是不写,系统用默认名
    t.start()  # 给操作系统发信号,几乎是瞬间开启新的线程

    t.setName("儿子线程1")  # 更改线程名字;  # 这个例子中,task中的第一个print,里面的线程名还是 “子线程1”,time.sleep(1)之后,第二个print的线程名就变成了“儿子线程1”

    print(active_count())  # 返回当前活跃的线程数
    print(enumerate())  # 返回当前所有活跃的线程对象;列表的形式  # active_count() 就相当于 len(enumerate())

    t.join()   #  主线程卡在这一步,等待t这个线程执行结束
    print(t.is_alive())  # 查看线程是否存活;返回bool值

    # print(t.getName())  # 在“主线程”中也可以查看“子线程”名
    print("主线程名字",current_thread().getName())
    current_thread().setName("主线程名")  

运行结果:

 

守护线程:无论是进程还是线程,都遵循:守护xx在主xx运行完毕后被销毁

需要强调的是:运行完毕并非终止运行

  1. 对主进程来说,运行完毕指的是主进程代码运行完毕;

  2. 对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕

详细解释:

  1. 主进程在其代码结束后就已经算运行完毕了(守护进程就在此时被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束;

  2. 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就会被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都会被回收,而进程必须保证非守护线程都运行完毕后才能结束


from threading import Thread
import time

def foo():
    print(123)
    time.sleep(1)
    print("end123")

def bar():
    print(456)
    time.sleep(2)
    print("end456")

if __name__ == "__main__":
    t1 = Thread(target=foo)
    t2 = Thread(target=bar)

    t1.daemon = True  # 把t1设置成守护线程; # 在start()之前设置
    t1.start()
    t2.start()

    print("main----")

# 运行结果:
# 123
# 456
# main----
# end123
# end456  

线程互斥锁:


from threading import Thread,Lock
import time

n = 100

def task():
    global n  # global n 不需要加锁
    mutex.acquire()  # 加锁就是为了局部串行; # 修改共享数据的部分需要加锁
    temp = n
    time.sleep(0.1)  # 0.1秒足以让“主线程”中的那100个“子线程”起来
    n = temp - 1
    mutex.release()  # 通过加锁效率降低

if __name__ == "__main__":
    mutex = Lock()   # threading下面的Lock
    t_list = []
    for i in range(100):
        t = Thread(target=task)
        t_list.append(t)
        t.start()

    for t in t_list:
        t.join()

    print("",n)
    
# 运行结果:
# 主 0  

 

GIL:global interpreter lock; 只有CPython才有GIL

GIL是加在CPython解释器上的互斥锁,本质也是一把互斥锁,所有互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享的数据只能被一个任务所修改,进而保证数据安全

保护不同的数据安全,就应该加不同的锁 (GIL保护的是解释器里关于垃圾回收线程的数据(解释器级别的数据),保护不了你自己的共享数据;想要保护你自己的共享数据,需要你自己加互斥锁)

要想了解GIL,需要确定一点: 每次执行python程序,都会产生一个独立的进程


# 运行一个py文件,启动的就是一个python解释器的进程(python.exe的)

# 运行python文件需要经历3步:
# 1. 先把python解释器的内容加载到内存
# 2. 把py文件的代码从硬盘加载到内存
# 3. 把py文件的内容交给Cpython解释器去执行(py文件里面的代码只是字符串,并不能运行;执行的是python解释器里面C给你实现的功能)
# 注: 第3步可以这么理解: 把python解释器当成一个函数,而你写的py文件代码是python解释器这个函数的参数,然后把py文件的代码当做参数传给python解释器这个函数
# 所以你运行一个py文件,产生的是一个python解释器的进程  

 

GIL与多线程:

有了GIL的存在,同一时刻同一进程中只有一个线程被执行;所以多线程无法利用多核优势

多进程还是多线程?首先要明白:

  1. CPU不是用来做IO的,而是用来做计算的;

  2. 多CPU,意味着可以有多个核并行完成计算,所以多核提升的是计算性能

  3. 每个CPU一旦遇到I/O阻塞,仍然需要等待,所以多核对I/O操作没什么用

结论:

  1. 对计算来说,CPU越多越好,但是对于I/O来说,再多的CPU也没用;

  2. 对运行一个程序来说,随着CPU的增多执行效率肯定会有所提高(不管提升幅度多大,总会有提高),这是因为一个程序基本不会是纯计算或者纯I/O,所以我们只能相对的去看一个程序到底是计算密集还是I/O密集型,从而得出用多线程还是多进程

现在的计算机基本上都是多核,python对于计算密集型的任务开多线程的效率并不能带来多大性能上的提升,甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率会有显著提升

 

多线程性能测试:

如果并发的多个任务是计算密集型:多进程效率高


# 计算密集型:多进程效率高

from multiprocessing import Process
from threading import Thread
import time,os

def task():
    res = 0
    for i in range(100000000):
        res *= i

if __name__ == "__main__":
    l = []
    print(os.cpu_count())  # 本机4核
    start = time.time()
    for i in range(4):
        p = Process(target=task)  # 耗时 16.18108034133911秒
        # p = Thread(target=task)    # 耗时 26.78442931175232秒
        l.append(p)
        p.start()

    for i in l:
        i.join()

    stop = time.time()

    print("run time is %s"%(stop-start))  

如果并发的多个任务是I/O密集型:多线程效率高


from multiprocessing import Process
from threading import Thread
import threading
import os,time
def work():
    time.sleep(2)
    # print('===>')

if __name__ == '__main__':
    l=[]
    print(os.cpu_count()) #本机为4核
    start=time.time()
    for i in range(400):
        p=Process(target=work) #耗时32s多,大部分时间耗费在创建进程上
        # p=Thread(target=work) #耗时2.0602962970733643 秒
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print('run time is %s' %(stop-start))  

应用:多线程用于IO密集型,如socket,爬虫,web等; 多进程用于计算密集型(多进程能用到多核优势),如金融分析

 

死锁与递归锁:

死锁:指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,他们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程;如下所示:


from threading import Thread,Lock
import time

mutexA = Lock()
mutexB = Lock()

class MyThread(Thread):
    def run(self):
        self.func1()
        self.func2()

    def func1(self):
        mutexA.acquire()
        print("%s 拿到A锁了"%self.name)  # Thread对象下面自带有 name这个属性

        mutexB.acquire()
        print("%s 拿到B锁了" % self.name)

        mutexB.release()
        mutexA.release()

    def func2(self):
        mutexB.acquire()
        print("%s 拿到B锁了" % self.name)
        time.sleep(1)

        mutexA.acquire()
        print("%s 拿到A锁了" % self.name)
        mutexA.release()

        mutexB.release()

if __name__ == "__main__":
    for i in range(5):
        t = MyThread()
        t.start()

# 运行结果:
# Thread-1 拿到A锁了
# Thread-1 拿到B锁了
# Thread-1 拿到B锁了
# Thread-2 拿到A锁了   # 出现死锁,整个程序阻塞住  

 

递归锁:

解决方法,递归锁:在python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock;

这个RLock内部维护着一个Lock和一个counter(计数器)变量,counter记录了acquire的次数,从而使得资源可以被多次acquire;知道一个线程中所有的acquire都被release,其他的线程才能获得资源;上面的例子如果使用RLock代替Lock,则不会发生死锁,二者的区别是:递归锁可以连续acquire多次,而互斥锁只能acquire一次


from threading import Thread,RLock
import time

mutexA = mutexB = RLock()  # 一个线程拿到锁,counter +1,该线程内又碰到加锁的情况,则counter持续 +1;这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止

class MyThread(Thread):
    def run(self):
        self.func1()
        self.func2()

    def func1(self):
        mutexA.acquire()
        print("%s 拿到A锁了"%self.name)

        mutexB.acquire()
        print("%s 拿到B锁了" % self.name)

        mutexB.release()
        mutexA.release()

    def func2(self):
        mutexB.acquire()
        print("%s 拿到B锁了" % self.name)
        time.sleep(1)

        mutexA.acquire()
        print("%s 拿到A锁了" % self.name)
        mutexA.release()

        mutexB.release()

if __name__ == "__main__":
    for i in range(5):
        t = MyThread()
        t.start()  

 

信号量:

信号量也是一种锁,可以指定信号量(例如5),对比互斥锁同一时间只能有一个任务抢到锁去执行,信号量同一时间可以有5个任务拿到锁去执行


from threading import Thread,Semaphore,current_thread
import time

def task():
    with sm:
        print("%s get sm"%current_thread().getName())
        time.sleep(1)

    """
    with sm: 和 with open(file)的用法是一样的:
    在with sm会先自动 sm.acquire(), with sm内的代码执行完后会自动 sm.release()
    """

if __name__ == "__main__":
    sm = Semaphore(3)   # 3是指信号量的最大计数量(最多有几把锁),即同一时间最有能有几个线程抢到锁去执行
    for i in range(10):
        t = Thread(target=task)
        t.start()

"""
Semaphore管理一个内置的计数器,
每当调用acquire()时内置计数器 -1; 调用 release()时内置计数器 +1;
计数器不能小于0,当计数器为0时, acquire()将阻塞线程直到其他线程调用 release()
"""  

 

Event事件:

线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其他线程需要通过判断某个线程的状态来确定自己下一步的操作,我们就需要使用threading库中的Event对象;对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在初始状况下,Event对象中的信号被设置为False;如果有线程等待一个Event对象,而这个Event对象的标志为False,那么这个线程将会被一直阻塞到该标志为True。一个线程如果将一个Event对象的信号标志设置为True,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为True的Event对象那么它将忽略这个事件,继续执行


from threading import Event
event.is_set()  # 返回event的状态值
event.wait()  # 如果 event.is_set() == False将阻塞线程  # wait(number)是设置最大等待时长(秒)
event.set()  # 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态,等待操作系统调度
event.clear()  # 恢复event的状态值为False  

 


from threading import Thread,Event,current_thread
import time

event = Event()  # 实例化一个Event对象

def conn():
    n = 0
    while not event.is_set():   # 这行代码的含义是:event还没有被设置成True,即 还没有经历 event.set()这一步 # .is_set()也该写成 .isSet()
        if n == 6:
            print("%s tried to connect too many times"%current_thread().getName())
            return
        print("%s is trying to connect %s times"%(current_thread().getName(),n))
        event.wait(0.5)  # 最多等0.5s
        n += 1
    print("%s is connected"%current_thread().getName())

def check():
    print("%s is checking"%current_thread().getName())
    time.sleep(5)
    event.set()  # 这一步将event设置成了True

if __name__ == "__main__":
    for i in range(3):
        t = Thread(target=conn)
        t.start()
    t = Thread(target=check)
    t.start()  

 

定时器(Timer):隔一段时间后执行某任务


from threading import Thread,Timer

def task(name):
    print("hello %s"%name)

timer = Timer(3,task,args=("neo",))  # Timer第一个参数interval传入时间间隔(单位:秒),意为几秒之后去执行后面的任务;第二个是函数名;后面传入函数需要传入的参数,两种方式:args和kwargs,args是原则的形式
timer.start()  # 启动这个定时器

"""
Timer是Thread的子类,Timer实例化后得到是一个线程对象
所以,timer.start()就是开启了一个新的线程
"""  

利用定时器Timer制作一个定时有效的验证码 :


from threading import Timer
import random

class Code:

    def __init__(self):
        self.code_cache()   # 实例化的时候就先生成一个验证码存放到cache里面

    def code_cache(self):
        self.cache = self.make_code()   # 生成的验证码放到缓存里面
        print(self.cache)
        self.timer = Timer(10,self.code_cache)   # 每隔10秒就重新生成一个线程去调用code_cache()方法
        self.timer.start()

    def make_code(self,n=4):
        res = ""
        for i in range(n):
            s1 = str(random.randint(0,9))
            s2 = chr(random.randint(65,90))  # 65-90是A-Z在ASCII码中对应的位置
            res += random.choice([s1,s2])   # 从s1(数字)和s2(A-Z)中随机选出一个添加到 res 里面
        return res

    def check(self):
        while True:
            code_ipt = input("输入您的验证码:").strip()   # 如果输入的验证码不正确,则每隔10就生成一个新的验证码
            if code_ipt.upper() == self.cache:
                print("验证码正确")
                self.timer.cancel()   # 关闭定时器
                return

code = Code()
code.check()  

 

线程queue:

 queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.

有三种不同的用法:

class queue.Queue(maxsize=0) # 队列:先进先出


import queue

q=queue.Queue()
q.put('first')
q.put('second')
q.put('third')

print(q.get())
print(q.get())
print(q.get())

'''
结果(先进先出):
first
second
third
'''  

 class queue.LifoQueue(maxsize=0)  # 堆栈:last in first out


import queue

q=queue.LifoQueue()
q.put('first')
q.put('second')
q.put('third')

print(q.get())
print(q.get())
print(q.get())

'''
结果(后进先出):
third
second
first
'''  

 

class queue.PriorityQueue(maxsize=0)  # 优先级队列: 存储数据时可设置优先级的队列


import queue

q=queue.PriorityQueue()
#put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
q.put((20,'a'))
q.put((10,'b'))
q.put((30,'c'))

print(q.get())
print(q.get())
print(q.get())

'''
结果(数字越小优先级越高,优先级高的优先出队):
(10, 'b')
(20, 'a')
(30, 'c')
'''  

 

基于多线程的套接字通信:

服务端:


from socket import *
from threading import Thread

def comm(conn):
    while True:
        try:
            data = conn.recv(1024)
       if not data: break conn.send(data.upper())
except ConnectionResetError: conn.close() break def server(ip,port): server = socket(AF_INET,SOCK_STREAM) server.bind((ip,port)) server.listen(5) while True: conn,client_addr = server.accept() t = Thread(target=comm,args=(conn,)) t.start() server.close() if __name__ == "__main__": server("127.0.0.1",8080)

"""
主线程也是一个线程,所以可以让主线程去做建链接的任务,开启新的“子线程”去做通信的任务
"""

 

线程池进程池:(池的目的是对数目加以限制)

基于多进程或多线程能实现并发的套接字通信,然而这种实现方式的致命缺陷是:服务端开启的进程数或线程数都会随着并发的客户端数目增多而增多,这会对服务端主机带来巨大的压力甚至瘫痪,所以我们必须要对服务端开启的进程数或线程数加以控制,让机器在一个自己可以承受的范围内运行,这就是进程池或线程池的用途,例如进程池就是用来存放进程的池子,本质还是基于多进程,只不过是对开启进程的数目加上了限制


concurrent.futures模块提供了高度封装的异步调用接口
ThreadPoolExecutor # 线程池,提供异步调用
ProcessPoolExecutor  # 进程池,提供异步调用  

# 进程池:

from
concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor import os,time,random def task(name): print("name:%s pid:%s run"%(name,os.getpid())) time.sleep(random.randint(1,3)) if __name__ == "__main__": pool = ProcessPoolExecutor(4) # 4表示池子里面最多能放的进程数;如果不写,默认是CPU数 for i in range(10): pool.submit(task,"neo%s"%i) # pool.submit(func,args,kwargs) 第一个参数写函数名,后面写函数需要传入的参数;# pool.submit()是异步提交任务 # 异步提交任务:提交完任务后立马就走,不需要管任务有没有起来,也不需要拿结果 print("") """ 进程池 pool = ProcessPoolExecutor(4) 从始至终最多只会有4个进程;如下图运行结果的pid所示 """

线程池:


from concurrent.futures import ThreadPoolExecutor
from threading import current_thread
import os,time,random

def task():
    print("thread name:%s pid:%s run"%(current_thread().getName(),os.getpid()))
    time.sleep(random.randint(1,3))

if __name__ == "__main__":
    pool = ThreadPoolExecutor(5)
    for i in range(10):
        pool.submit(task)

    pool.shutdown()  # shutdown()的作用是:相当于pool.close() + pool.join(),即关闭线程池的入口,并且等待池内所有线程执行完毕; # 默认 shutdown(wait=True)

    print("")  

 提交任务的两种方式:

  1. 同步调用:提交完任务后,就在原地等待任务执行完毕,拿到结果,在执行下一行代码(会导致程序变成串行执行)

  2. 异步调用:提交完任务后,不会等待任务执行完毕

回调函数: 可以为进程池或线程池内的每个进程或线程绑定一个函数,该函数在进程或线程的任务执行完毕后自动触发,并接收任务执行完后的对象当做参数传入,该函数称为回调函数


from concurrent.futures import ThreadPoolExecutor
import time,random

def task1(name):
    print("%s is running"%name)
    time.sleep(random.randint(2,3))
    length = random.randint(5,12)*"#"
    return {"name":name,"length":length}

def task2(obj):
    value_of_task1 = obj.result()  # obj是pool.submit(task1,"neo")执行完后的一个对象,需要利用 result()方法取出对象中的值,即 task1 return出来的值
    name = value_of_task1["name"]
    length = len(value_of_task1["length"])
    print("NAME:%s  LENGTH:%s"%(name,length))

if __name__ == "__main__":
    pool = ThreadPoolExecutor(13)

    pool.submit(task1,"neo").add_done_callback(task2)  # 这就是异步调用函数; # pool.submit(task1,"neo") 异步调用,执行完毕后会自动调用后面的task2函数,task2这个函数有且只能有一个参数,在给task2传参数时,会把pool.submit(task1,"neo")当成一个对象传入task2的那个参数中,所以task2中需要利用 result()把这个对象中的值取出来

    pool.submit(task1,"alex").add_done_callback(task2)
    pool.submit(task1,"egon").add_done_callback(task2)

"""
同步调用的方法:
如在 __main__ 中
res = pool.submit(task1,"neo").result()   # 同步调用; 此时提交完任务后程序就在这一步原地等待,直到新开的线程task1执行完毕后再利用 result()得到了task1中的返回值,程序才继续往下执行
"""  

 

模拟爬去数据:


# requests模块可模拟浏览器的行为
# requests.get(url) # 到目标地址去下载一个文件到本地
# requests.get(url).text # 目标网页的内容


# 模拟爬虫:异步调用+回调函数的应用
import requests,time
from concurrent.futures import ThreadPoolExecutor

def get(url):
    print("getting %s"%url)
    response = requests.get(url)
    time.sleep(3)  # 模拟网络IO
    return {"url":url,"content":response.text}  # response.text是所下载网页中的内容

def parse(res):
    res = res.result()  # 取得 pool.submit(get,url)的结果
    url = res["url"]
    length = len(res["content"])
    print("parse %s res is %s"%(url,length))

if __name__ == "__main__":
    urls = [
        "https://docs.python.org/3/library/",
        "https://www.python.org/",
        "https://pypi.python.org/pypi"
    ]
    # 应该采用异步调用,保证爬取任务能并发执行
    # 爬去任务是IO密集型,因为get任务会遇到网络IO(下载网页等),所以应该采用多线程
    # 线程也不能无限增加,需要对线程数量做一个设置,所以应该采用 concurrent.futures 的 ThreadPoolExecutor

    pool = ThreadPoolExecutor(2)  # 提交了3个任务,但线程池最多能容纳2个,所以第3个任务会等池子中的某个任务执行完毕后再去执行
    for url in urls:
        pool.submit(get,url).add_done_callback(parse)  

线程池的套接字通信:


from socket import *
from concurrent.futures import ThreadPoolExecutor

def comm(conn):
    conn = conn
    while True:
        try:
            data = conn.recv(1024)
            if not data: break
            conn.send(data.upper())
        except ConnectionResetError:
            conn.close()
            break

def server(ip,port):
    server = socket(AF_INET,SOCK_STREAM)
    server.bind((ip,port))
    server.listen(5)

    while True:
        conn,client_addr = server.accept()
        pool.submit(comm,conn)  # 直接把comm放入线程池中就行,不需要回调函数

    server.close()

if __name__ == "__main__":
    pool = ThreadPoolExecutor(2)
    server("127.0.0.1",8081)  # 主线程还是用来干建链接的活

# 运行结果:
# 通过线程池控制了线程数,同一时间最多只能有两个服务端通信  

 

转载于:https://www.cnblogs.com/neozheng/p/8666563.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

网络编程进阶:并发编程之多线程 的相关文章

  • STM32运行时程序卡在B.处

    STM32运行时程序卡在B 处的问题处理的一天多 xff0c 终于找到原因 1 xff0c 表现 我所使用的芯片是stm32f427vit6 xff0c stm32f4系列芯片外设多数都相同 xff0c 407 xff0c 405的 用户也
  • Keil 查看文件路径的方法

    目录 方法一 xff1a 方法二 xff1a 方法一 xff1a 在工程种选择任意一个文件 xff0c 然后点击右键 xff0c 选择 34 Option for File 34 就可以看到这个文件的路径了 方法二 xff1a 在文件框种右
  • Linux下CMake简明教程

    CMake是开源 跨平台的构建工具 xff0c 可以让我们通过编写简单的配置文件去生成本地的Makefile xff0c 这个配置文件是独立于运行平台和编译器的 xff0c 这样就不用亲自去编写Makefile了 xff0c 而且配置文件可
  • unity3d TextMeshPro使用中文字体(秒懂)

    1 打开C Windows Fonts 目录选择中文字体 2 将选中的字体拖拽到Unity编辑器中 3 将已下这段文字保存至zh cn txt文件 注意编码格式为Unicode xff0c 不然会出现乱码 并且拖入Unity编辑器 的一是了
  • Windows下cmd命令行远程传输sftp命令

    sftp 用户名 64 ip地址
  • laravel 500 Server Error,完美解决

    在安装laravel项目后 xff0c 首次打开laravel出现 解决方案 xff1a 1 打开配置文件 laravel config app php 3 找到 39 debug 39 项 设置为 true 4 刷新网页 抛出运行异常 x
  • ubuntu下C++两种方法解析json

    第一种 用jsoncpp xff0c 不过本人目前不知道如何在clion里面使用 xff0c 只知道在命令行g 43 43 使用 xff0c 哪位知道如何在clion里使用可以告诉我 xff0c 谢谢 如下步骤本人操作过两边 xff0c 所
  • unity3d鼠标控制物体 移动 旋转 缩放

    需求 xff1a 制作一个鼠标中键移动缩放和鼠标右键旋转的功能 xff08 鼠标右键旋转可以自行改成鼠标左键旋转 xff09 效果图 xff1a 1 新建一个名为Target的立方体cube 2 新建一个材质球 Yellow颜色的并挂载到T
  • Centos Apache2设置禁止浏览目录,绝对生效。

    1 打开httpd配置文件 vi etc httpd conf httpd conf 2 找到 Options Indexes FollowSymLinks 注释掉 xff0c 并在下面添加一行 Options None Options I
  • Ubuntu查看某端口是否开放

    1 例如查看80端口是否开放 xff0c 输入 lsof i 端口号 xff0c 如果没有信息出现则说明该端口还未开放 lsof i 80 telnet 192 168 0 1 80 2 也可以通过以下命令 xff0c 查看所有开放的端口
  • 集群和分布式的区别,还不理解的你打我!!!

    1 老师布置作业 xff0c 让你抄10遍白居易的 长恨歌 xff0c 抄一份需要5分钟 xff0c 10份需要50分钟 xff0c 这时你觉得要是有9个人帮我抄另外9份 xff0c 那你5分钟就能交作业了 xff0c 这就是集群处理 2
  • windows下查找并关闭端口

    1 查找端口 8080 gt netstat ano findstr 8080 找到8080端口对应的pid 19060 2 关闭8080端口 gt taskkill f pid 19060
  • Unity3D 制作调色板

    一个很好用的调色板 源码 xff1a http websol cn 2021 01 27 unity e8 b0 83 e8 89 b2 e6 9d bf e6 ba 90 e7 a0 81
  • vs2017生成C++/C语言的DLL以及调用 极简讲解 秒懂

    为了尽可能缩减文字描述 xff0c 减少阅读疲劳 xff0c 就直接上图了 1 VS2017下C 43 43 创建dll动态链接库 2 VS2017下C语言代码创建dll动态链接库 xff08 导出方式较于C 43 43 有点差别 xff0
  • Unity页面滑动Slider

    第一步 xff1a 创建三个Image xff08 或者多个都行 xff09 xff0c 并且设置Canvas的CanvasScaler脚本组件UI Scale Mode和Reference Resolution的值 设置Canvas 第二
  • linux的TCP连接数量真的不能超过65535个吗?

    原文链接 xff1a https blog csdn net daocaokafei article details 115410761 首先 xff0c 问题中描述的65535个连接指的是客户端连接数的限制 在tcp应用中 xff0c s
  • 关于网页实现串口或者TCP通讯的说明

    概述 最近经常有网页联系我 xff0c 反馈为什么他按我说的方法 xff0c 写的HTML代码 xff0c 无法在chrome网页中运行 这里我统一做一个解释 xff0c 我发现好多网页并没有理解我的意思 其实 xff0c 要实现在HTML
  • Intel RealSense(实感技术)概览

    Intel RealSense 实感技术 概览 1 Reply 版权声明 xff1a 本文系本站作者自己翻译整理 xff0c 欢迎转载 xff0c 但转载请以超链接形式注明文章来源 planckscale info 作者信息和本声明 xff
  • postman安装使用教程

    无聊的夜晚 xff0c 没有酒喝 xff0c 也没妹子陪 xff0c 闲来没事研究下postman xff0c 之前接触过有点 xff0c 还不错的工具 先从最基本的开始 postman是谷歌的一款非常好用的工具 xff0c 可用来做手工的
  • 接口测试 requests的身份认证方式

    requests提供多种身份认证方式 xff0c 包括基本身份认证 netrc 认证 摘要式身份认证 OAuth 1 认证 OAuth 2 与 OpenID 连接认证 自定义认证 这些认证方式的应用场景是什么呢 xff1f 身份认证的定义

随机推荐

  • 使用libcurl提交POST请求

    最近在学习libcurl xff0c 并利用它提交POST请求 xff0c 可是返回的响应总是无从验证该次POST请求是否成功提交了 1 先看下根据firebug提交的一次成功的请求 xff0c 这里以login我喜欢上的xiami为例 x
  • TX2安装Realsense -L515相机并在ros下 运行yolo 总结(3)

    前面提到可以移植yolov4到平台上 这里给出几个参考链接 xff0c 重在学习移植方法 xff1a 首先是大佬的 xff1a https span class token operator span span class token op
  • VS2013 配置使用微软开源sdk: C++ REST SDK 及运行官方的 JSON例子

    转至 https blog csdn net sdsabc2000 article details 53706632 utm medium 61 distribute pc relevant none task blog BlogComme
  • Python爬虫学习4:requests.post模拟登录豆瓣(包括获取验证码)

    1 在豆瓣登录网页尝试登录后打开开发者工具 xff0c 可以查找后去Headers和Form Data信息 2 实现代码 import requests import html5lib import re from bs4 import B
  • STM32 | STM32CubeMX基础之USART

    一 USART框图 功能引脚 TX xff1a 发送引脚 xff0c Usart1一般对应PA9 RX xff1a 接收引脚 Usart1一般对应PA10 SW RX xff1a 数据接收引脚 xff0c 只用于单线和智能卡模式 xff0c
  • Android CMake 编译传递宏定义参数

    在做 C 43 43 需求开发时经常会遇到用宏定义来区分不同版本 不同平台的功能 xff0c 如下所示 xff1a ifdef DEBUG 调用 debug 版本方法 elif RELEASE 调用 release 版本方法 endif 在
  • FreeRTOS高级篇2---FreeRTOS任务创建分析

    在FreeRTOS基础系列 FreeRTOS系列第10篇 FreeRTOS任务创建和删除 中介绍了任务创建API函数xTaskCreate xff0c 我们这里先回顾一下这个函数的声明 xff1a BaseType t xTaskCreat
  • 调用百度OCR API实现身份证文字识别

    调用百度OCR API实现身份证文字识别 通过调用百度OCR的两个接口 xff0c 实现身份证图像识别 首先要在百度云注册账号 xff0c 并创建应用 xff0c 以获取AppID xff0c API Key xff0c Secret Ke
  • 一个基于TCP/IP的小项目,实现广播消息的功能。(超详细版)

    1 结合现状 功能分析 该功能基于上个项目的改进 xff0c 主要是通过对服务器端代码的修改 xff0c 以及对客户端作少许修改 xff0c 实现开启多客户端时 xff0c 一个客户端发送消息 xff0c 达到对所有客户端广播的效果 可参考
  • 中断方式和查询方式的区别?

    中断方式 xff1a 是事件触发的 xff0c 换訖只要有事件产生都会进入中断 組取得最优运行 xff0c 因此响应更快 xff0c 及时 查询方式 xff1a 就是在主函数里面不停循环 xff0c 查询端C 状态 xff0c 明显其弊端在
  • [源码解读]position_estimator_inav_main解读(如何启动光流)

    阅读工具 xff1a source insight 技术交流 xff1a zinghd 64 163 com xff0c 757012902 64 qq com 源码版本 xff1a Firmware xff08 原生固件 xff09 如有
  • C++ 程序编译过程:从代码到程序

    在大学课堂上学习 C 43 43 时 xff0c 老师并没有过多涉猎 C 43 43 语法背后的知识 也就是说 xff0c 初学 C 43 43 时 xff0c 哪怕写出了代码 xff0c 我也并不知道从代码到程序的过程中究竟发生了什么 我
  • 05-STM32F1 - 串行通信1-UART(3),printf,scanf重定向

    05 STM32F1 串行通信UART xff0c printf xff0c scanf重定向 在C 语言标准库中 xff0c fputc 函数是printf 函数内部的一个函数 xff0c 功能是将字符ch 写入到文件指针file所指向文
  • 指针作为函数参数 进行内存释放 并置NULL

    author xff1a 张继飞 写在前面 xff0c 前面写了代码封装free函数 xff0c 但是调用封装并退出后 xff0c 指针并不为NULL xff0c 导致接下来以此作为判断条件的时候就出现问题了 先前封装函数为void fre
  • R40 gpio 寄存器地址操作【原创】

    首先要学会看手册 xff0c 下面拿PC来做说明 3 18 3 Register List Module Name Base Address PIO 0x01C20800 Register Name Offset Description P
  • 关于basic认证和digest认证的初步理解

    1 basic认证是把用户和密码通过base64加密后发送给服务器进行验证 2 digest认证则是把服务器响应的401消息里面的特定的值和用户名以及密码结合起来进行不可逆的摘要算法运算得到一个值 xff0c 然后把用户名和这个摘要值发给服
  • STM32中USART的使用方法

    USART作为一种标准接口在应用中十分常见 本文着重分析其作为 UART的配置和应用方法 1 STM32固件库使用外围设备的主要思路 在 STM32中 xff0c 外围设备的配置思路比较固定 首先是使能相关的时钟 xff0c 一方面是设备本
  • 利用 __FILE__, __LINE__输出debug信息

    include lt stdio h gt define DEBUG ifdef DEBUG define DEBUG format printf 34 File 34 FILE 34 Line 05d 34 format 34 n 34
  • 认证、授权、鉴权和权限控制

    原文地址 https www cnblogs com badboyh2o p 11068779 html 如有侵权 xff0c 请联系删除 xff0c 谢谢 xff01 本文将对信息安全领域中认证 授权 鉴权和权限控制这四个概念给出相应的定
  • 网络编程进阶:并发编程之多线程

    多线程 xff1a 在传统操作系统中 xff0c 每个进程有一个地址空间 xff0c 而且默认就有一个控制线程 xff1b 进程的作用就是隔离数据 进程只是用来把资源集中到一起 xff08 进程只是一个资源单位 xff0c 或者说资源集合