C++多线程编程超详解

目录
  • C++多线程
  • 1. 概念
  • 2. 常用API
    • 1.thread
    • 2.互斥锁mutex
    • 3. 挂起和唤醒
  • 3. 应用场景
    • 3.1 call_once执行一次的函数
    • 3.2 condition_variable条件锁
    • 3.3 future获取线程的计算结果
    • 3.4 promise主线程如何将数据发送数据到其他线程
    • 3.5 future.share()多线程之间共享状态
    • 3.6 线程packaged_task
    • 3.7 时间约束
  • 4. Windows多线程
    • 4.1 Windows创建线程
    • 4.2 Windows互斥锁
    • 4.3 Windows挂起和唤醒线程
  • 总结

C++多线程

1. 概念

  • 进程:一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在Windows系统中,一个运行的xx.exe就是一个进程。
  • 线程:进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
  • 并发:并发指的是两个或多个独立的活动在同一时段内发生。并发在生活中随处可见:比如在跑步的时候同时听音乐,在看电脑显示器的同时敲击键盘等。同一时间段内可以交替处理多个操作,强调同一时段内交替发生。
  • 并行:同一时刻内同时处理多个操作,强调同一时刻点同时发生。

2. 常用API

​ 头文件#include<thread>

1.thread

API 描述 注意
thread.join() 加入线程(会阻塞主线程,模拟同步操作)
thread.detach() 加入线程(不会阻塞主线程,模拟异步操作)
thread.joinable() 是否可加入线程,返回bool
thread.get_id() 获取线程的ID
thread.hardware_concurrency() 获取硬件并发的数量
thread.swap() 交换线程
thread.native_handle() 获取原生handle,为windows多线程中CreateThread的返回值,使用这个handle从而可以实现线程的挂起唤醒

测试代码:

void threadFunc01() {
	cout << "thread join1" << endl;
	this_thread::sleep_for(chrono::seconds(2));
}
void threadFunc02() {
	cout << "thread join2" << endl;
	this_thread::sleep_for(chrono::seconds(2));
}
void test01() {
	// 创建线程
	std::thread thread1(threadFunc01);
	std::thread thread2(threadFunc02);
	//thread.join(); //join 会阻塞主线程 同步操作
	//thread.detach(); //detach 不会阻塞主线程 异步操作
	bool bJoinAble = thread1.joinable();
	thread::id threadId = thread1.get_id();
	//hardware_concurrency 硬件并发的数量
	int threadNum = thread1.hardware_concurrency();
	cout << "hardware_concurrency:" << threadNum << endl;
	//应用 线程的预分配。
	for (int i = 0; i < thread1.hardware_concurrency(); i++) {
		std::thread threadRef(threadFunc01);
		threadRef.detach();
	}
	thread1.swap(thread2);
	thread1.join();
}

向线程里传递参数的方法

// 向线程里传递参数的方法
#include<string>
void threadFunc03(int num, const string& str) {
	cout << "num = " << num << " str = " << str << endl;
}
struct FObject {
	void Run(const string& str) {
		cout << str << endl;
	}
};
void test02() {
    // 通过函数绑定
	thread newThread1(threadFunc03, 10, "Unreal");
	newThread1.detach();
	// 通过lambda绑定
	int a = 50;
	thread newThread2([&](int num,const string& str) {
		cout << "a = " << a << " num = " << num << " str = " << str << endl;
		}, 1, "Unreal");
	newThread2.detach();

	// 绑定对象
	FObject objectRef;
	thread newThread3(&FObject::Run, objectRef, "Unreal");
	newThread3.detach();
}

2.互斥锁mutex

​ 头文件#include<mutex>

API 描述 注意
mutex.lock() 上锁
mutex.unlock() 解锁
mutex.try_lock() 判断可不可以加锁,返回bool 可以用该方法建立非阻塞模式

测试代码:

#include<mutex>
mutex lockRef;
void threadFunc04(int num,const string& str) {
	// 进入该线程锁住该线程,其他线程想要进入该线程需要排队
	lockRef.lock();
	cout << "thread join4" << endl;
	this_thread::sleep_for(chrono::seconds(2));
	// 解锁
	lockRef.unlock();
}
void test03() {
	std::thread thread1(threadFunc04, 10, "Unreal");
	std::thread thread2(threadFunc04, 5, "Unity");
	std::thread thread3(threadFunc04, 20, "Cocos");
	thread1.detach();
	thread2.detach();
	thread3.detach();
}

使用类加锁的方式:

#include<mutex>
mutex lockRef;
struct FEvent {
	FEvent() {
		m.lock();
	}
	~FEvent()
	{
		m.unlock();
	}
	static mutex m;
};
mutex FEvent::m;
#define LOCK_SCOPE FEvent Event
void threadFunc04(int num,const string& str) {
	LOCK_SCOPE; //加上锁,并且过了这个作用域自动解锁(析构)
	cout << "thread join4" << endl;
	this_thread::sleep_for(chrono::seconds(2));
}
void test03() {
	std::thread thread1(threadFunc04, 10, "Unreal");
	std::thread thread2(threadFunc04, 5, "Unity");
	std::thread thread3(threadFunc04, 20, "Cocos");
	thread1.detach();
	thread2.detach();
	thread3.detach();
}

try_lock()

void threadFunc04(int num,const string& str) {
	bool bLock = FEvent::m.try_lock();
	if (bLock) {
		LOCK_SCOPE; //加上锁,并且过了这个作用域自动解锁(析构)
		cout << "thread join4" << endl;
		this_thread::sleep_for(chrono::seconds(2));
	}
}

​ 使用try_lock()可以进行判断能不能上锁,不能上锁的话,就不用执行上锁后的代码,防止其他线程阻塞在该线程。

lock_guard

lock_guard是一种锁类,作用和我们上面自定义的锁类FEvent相同,创建的时候锁住目标线程,释放的时候解锁。

// 声明方式
lock_guard<mutex>ref;

源码:

template <class _Mutex>
class lock_guard { // class with destructor that unlocks a mutex
public:
    using mutex_type = _Mutex;
    explicit lock_guard(_Mutex& _Mtx) : _MyMutex(_Mtx) { // construct and lock
        _MyMutex.lock();
    }
    lock_guard(_Mutex& _Mtx, adopt_lock_t) : _MyMutex(_Mtx) { // construct but don't lock
    }
    ~lock_guard() noexcept {
        _MyMutex.unlock();
    }
    lock_guard(const lock_guard&) = delete;
    lock_guard& operator=(const lock_guard&) = delete;
private:
    _Mutex& _MyMutex;
};

unique_lock

​ 作用和lock_guard相同,唯一的不同之处,lock_guard开放的API只有析构函数,而unique_lock开放的API非常多,即自由度比lock_guard高,可以定义锁的行为。

void test05() {
	// defer_lock 关键字为延迟锁,即创建该对象时不会锁住该线程,什么时候锁需要自定义
	std::unique_lock<mutex>lockRef2(FEvent::m,defer_lock);
	std::unique_lock<mutex>lockRef2(FEvent::m,chrono::seconds(2)); //锁两秒
	//....执行
	lockRef2.lock();
	lockRef2.unlock();
	bool bLock1 = lockRef2.try_lock();//尝试上锁
	lockRef2.try_lock_for(chrono::seconds(2)); //锁2s
    mutex *lockRef3 = lockRef2.release(); //释放锁,同时会返回被释放的这个锁的指针对象
    bool bLock2 = lockRef2.owns_lock(); //当前是否被锁住
}

应用:

void test05() {
	//std::lock_guard<mutex>lockRef1(FEvent::m);
	// defer_lock 关键字为延迟锁
	std::unique_lock<mutex>lockRef2(FEvent::m,defer_lock);
	lockRef2.lock();
	lockRef2.mutex();
	bool bLock = lockRef2.owns_lock();
	std::unique_lock<mutex>lockRef3;
	lockRef2.swap(lockRef3);
	std::unique_lock<mutex>lockRef4 = move(lockRef3);
	lockRef4.unlock();
}

3. 挂起和唤醒

​ 头文件#include<windows.h>

1111111

111111

111111

11111

111111

测试代码:

#include<windows.h>
void threadFunc05() {
	while (true)
	{
		Sleep(10);
		cout << "threadFunc05" << endl;
	}
}

void test04() {
	thread thread1(threadFunc05);
	// 挂起线程
	SuspendThread(thread1.native_handle());
	Sleep(2);
	// 唤醒线程
	ResumeThread(thread1.native_handle());
}

如何高效将主线程资源进行转移:

void threadFunc06(const char* str) {
	cout << str << endl;
}
void test04() {
	// 如何高效转移线程资源
	// 使用std::move
	thread thread2(threadFunc06, move("Unreal")); // 使用move避免了拷贝
	thread thread3 = move(thread2);
	thread3.detach();
}

3. 应用场景

3.1 call_once执行一次的函数

​ 通过使用该函数,用来防止多线程的多次触发。

once_flag tag;
void callonceTest() {
	call_once(tag, [&]() {
		cout << "Do once" << endl;
		});
}
void test06() {
	for (int i = 0; i < 10; i++) {
		thread thread1(callonceTest);
		thread1.detach();
	}
}

3.2 condition_variable条件锁

​ 使用需要包含头文件#include<condition_variable>

可以使用条件锁来达到同步的作用,即当满足一定的条件后才解锁某个线程。

#include<condition_variable>
condition_variable condition_lock;
mutex mutexLock;
void conditionFuncTest() {
	unique_lock<mutex>lock(mutexLock);
	condition_lock.wait(lock);  //锁住该线程
	cout << "Run" << endl;
}
void test12() {
	std::thread threadRef(conditionFuncTest);
	threadRef.detach();
	Sleep(3000); //3s后再激活
	condition_lock.notify_one();
}

3.3 future获取线程的计算结果

​ 通过使用future可以得到"未来"线程被调用的时候计算得返回值,使用时需要包含头文件#include<future>。

声明方式:

// async为创建该线程的方式为异步 funName 函数名 args为传入的函数参数
std::future<string>newFuture = std::async(launch::async, funName,args...);

应用:

#include<future>
string getString(int num) {
	return "Unreal";
}
void test08() {
	std::future<string>newFuture = std::async(launch::async, getString, 10);
	//std::future<string>newFuture = std::async(launch::deferred, getString, 10); // 睡一秒再执行
	Sleep(1000);
	string str = newFuture.get(); //get只能调用一次 调第二次会崩溃
	// 防止崩溃的写法
	if (newFuture.valid()) {
		string str = newFuture.get();
	}
}

3.4 promise主线程如何将数据发送数据到其他线程

​ 通过使用promise(承诺)来进行进程之间的交互,常配合std::future使用。其作用是在一个线程t1中保存一个类型typename T的值,可供相绑定的std::future对象在另一线程t2中获取。

​ 测试代码:

// promise
string promiseTest(future<string>& future) {
	cout << future.get() << endl;
	return "Unreal";
}
void test09() {
	promise<string> promiseRef;
	future<string>future1 = promiseRef.get_future();
	future<string>future2 = std::async(launch::async, promiseTest, std::ref(future1)); //future 不支持值拷贝 需要传递引用
	promiseRef.set_value("Unreal is the best game engine in the world");
}

​ 但这里也有一个问题需要思考,如果需要发送数据到多个线程,是不是需要一个个的创建上面的代码呢。这里就引出了多线程之间共享状态这个解决方法。

3.5 future.share()多线程之间共享状态

​ 通过future.share()我们可以很方便的使多个线程之间共享状态。

现在来看看没有使用该函数的话我们要共享状态的话需要这么写:

string promiseTest(future<string>& future) {
	cout << future.get() << endl;
	return "Unreal";
}
void test09() {
	promise<string> promiseRef;
	future<string>future1 = promiseRef.get_future();
	future<string>future2 = promiseRef.get_future();
	future<string>future3 = promiseRef.get_future();
	future<string>future4 = std::async(launch::async, promiseTest, std::ref(future1)); //future 不支持值拷贝 需要传递引用
	future<string>future5 = std::async(launch::async, promiseTest, std::ref(future2)); //future 不支持值拷贝 需要传递引用
	future<string>future6 = std::async(launch::async, promiseTest, std::ref(future3)); //future 不支持值拷贝 需要传递引用
	promiseRef.set_value("Unreal is the best game engine in the world");
}

使用了future.share()函数后:

string promiseTest02(shared_future<string> future) {
	cout << future.get() << endl;
	return "Unreal";
}
void test09() {
	promise<string> promiseRef;
	future<string>future1 = promiseRef.get_future();
    // shared_future
	shared_future<string> sharedFutrue1 = future1.share();
	future<string>future2 = std::async(launch::async, promiseTest02, sharedFutrue1); //shared_future 可以用拷贝传递
	future<string>future3 = std::async(launch::async, promiseTest02, sharedFutrue1);
	future<string>future4 = std::async(launch::async, promiseTest02, sharedFutrue1);
	promiseRef.set_value("Unreal is the best game engine in the world");
}

3.6 线程packaged_task

​ packaged_taskpromise非常相似,packaged_task<F>是对promise<T= std::function<F>>中T= std::function<F>这一可调对象(如函数、lambda表达式等)进行了包装,简化了使用方法。并将这一可调对象的返回结果传递给关联的future对象。

绑定Lambda

void test10() {
	//绑定lambda
	packaged_task<int(int, int)> task1([](int a,int b) ->int{
		return a + b;
		});
	task1(1, 4);
	this_thread::sleep_for(chrono::seconds(1));
	if (task1.valid()) {
		auto f1 = task1.get_future();
		cout << f1.get() << endl;
	}
}

绑定普通函数

int packagedTest(int a,int b) {
	return a + b;
}
void test10() {
	//绑定函数
	packaged_task<int(int, int)>task2(packagedTest);
	task2(10, 5);
	this_thread::sleep_for(chrono::seconds(1));
	if (task2.valid()) {
		auto f2 = task2.get_future();
		cout << f2.get() << endl;
	}
}

使用std::bind进行函数绑定

int packagedTest(int a,int b) {
	return a + b;
}
void test10() {
	// bind
	packaged_task<int(int, int)>task3(std::bind(packagedTest,1,2));
	task3(10, 5); //因为bind使用了占位符 所以这里传入的10 5失效了
	this_thread::sleep_for(chrono::seconds(1));
	if (task3.valid()) {
		auto f3 = task3.get_future();
		cout << f3.get() << endl; //1+2
	}
}

3.7 时间约束

void test11() {
	//休眠2s
	this_thread::sleep_for(chrono::seconds(2));
	// 休眠现在的时间加上2s
	chrono::steady_clock::time_point timePos = chrono::steady_clock::now() + chrono::seconds(2);
	this_thread::sleep_until(timePos);
}

4. Windows多线程

​ 使用WindowsAPI进行多线程的编写,需要包含头文件

#include<windows.h>

4.1 Windows创建线程

​ 使用CreateThread()创建线程

DWORD WINAPI funcThread(LPVOID lpPram) {
    // DWORD 类型为unsigned long
    // LPVOID 类型为void
    cout << "Unreal!" << endl;
    Sleep(1000);
    return 0l;
}
void windowsThreadTest01() {
	HANDLE handleRef = CreateThread(nullptr,0, funcThread,nullptr,0,nullptr);
    Sleep(2000);
    CloseHandle(handleRef); //使用之后需要关闭handle
}

​ 其中传入的参数为:

/*
WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
CreateThread(
    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,  和线程安全有关 一般为null
    _In_ SIZE_T dwStackSize,                            线程栈的大小
    _In_ LPTHREAD_START_ROUTINE lpStartAddress,         被线程执行的回调函数
    _In_opt_ __drv_aliasesMem LPVOID lpParameter,       传入线程的参数
    _In_ DWORD dwCreationFlags,                         创建线程的标志   参数0 代表立即启动该线程
    _Out_opt_ LPDWORD lpThreadId                        传出的线程ID
);
*/

4.2 Windows互斥锁

// windows互斥锁
HANDLE hMutex = nullptr;
DWORD WINAPI funcThread02(LPVOID lpParam) {
    cout << "Unreal" << endl;
    WaitForSingleObject(hMutex, INFINITE);
    Sleep(5000);
    ReleaseMutex(hMutex);
    return 0l;
}

void windowsThreadTest02() {
    hMutex = CreateMutex(nullptr, false, L"Mutex");
    HANDLE handleRef1 = CreateThread(nullptr, 0, funcThread02, nullptr, 0, nullptr);
    HANDLE handleRef2 = CreateThread(nullptr, 0, funcThread02, nullptr, 0, nullptr);
    CloseHandle(handleRef1);
    CloseHandle(handleRef2);
}

传入的参数为:

/*
WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
CreateMutexW(
    _In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes,      和线程安全有关一般为null
    _In_ BOOL bInitialOwner,                               有没有该锁的控制权
    _In_opt_ LPCWSTR lpName                                锁名字
    );
*/

4.3 Windows挂起和唤醒线程

​ 通过使用SuspendThread(HandleRef)和ResumeThread(HandleRef)来挂起和唤醒线程

// windows 挂起唤醒
DWORD WINAPI funcThread03(LPVOID lpParam) {
    while (true) {
        Sleep(500);
        cout << "IsRunning" << endl;
    }
    return 0l;
}

void windowsThreadTest03() {
    HANDLE hRef = CreateThread(nullptr, 0, funcThread03, nullptr, 0, nullptr);
    SuspendThread(hRef);
    Sleep(2000);
    ResumeThread(hRef);
    CloseHandle(hRef);
}

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • c++11多线程编程之std::async的介绍与实例

    本节讨论下在C++11中怎样使用std::async来执行异步task. C++11中引入了std::async 什么是std::async std::async()是一个接受回调(函数或函数对象)作为参数的函数模板,并有可能异步执行它们. template<class Fn, class... Args> future<typename result_of<Fn(Args...)>::type> async(launch policy, Fn&& fn

  • C++11中多线程编程-std::async的深入讲解

    前言 C++11中提供了异步线程接口std::async,std::async是异步编程的高级封装,相对于直接使用std::thread,std::async的优势在于: 1.std::async会自动创建线程去调用线程函数,相对于低层次的std::thread,使用起来非常方便: 2.std::async返回std::future对象,通过返回的std::future对象我们可以非常方便的获取到线程函数的返回结果: 3.std::async提供了线程的创建策略,可以指定同步或者异步的方式去创建

  • C++11并发编程:多线程std::thread

    一:概述 C++11引入了thread类,大大降低了多线程使用的复杂度,原先使用多线程只能用系统的API,无法解决跨平台问题,一套代码平台移植,对应多线程代码也必须要修改.现在在C++11中只需使用语言层面的thread可以解决这个问题. 所需头文件<thread> 二:构造函数 1.默认构造函数 thread() noexcept 一个空的std::thread执行对象 2.初始化构造函数 template<class Fn, class... Args> explicit th

  • c++11 多线程编程——如何实现线程安全队列

    线程安全队列的接口文件如下: #include <memory> template<typename T> class threadsafe_queue { public: threadsafe_queue(); threadsafe_queue(const threadsafe_queue&); threadsafe_queue& operator=(const threadsafe_queue&) = delete; void push(T new_va

  • C++多线程编程超详解

    目录 C++多线程 1. 概念 2. 常用API 1.thread 2.互斥锁mutex 3. 挂起和唤醒 3. 应用场景 3.1 call_once执行一次的函数 3.2 condition_variable条件锁 3.3 future获取线程的计算结果 3.4 promise主线程如何将数据发送数据到其他线程 3.5 future.share()多线程之间共享状态 3.6 线程packaged_task 3.7 时间约束 4. Windows多线程 4.1 Windows创建线程 4.2 W

  • linux下的C\C++多进程多线程编程实例详解

    linux下的C\C++多进程多线程编程实例详解 1.多进程编程 #include <stdlib.h> #include <sys/types.h> #include <unistd.h> int main() { pid_t child_pid; /* 创建一个子进程 */ child_pid = fork(); if(child_pid == 0) { printf("child pid\n"); exit(0); } else { print

  • java多线程编程技术详解和实例代码

     java多线程编程技术详解和实例代码 1.   Java和他的API都可以使用并发. 可以指定程序包含不同的执行线程,每个线程都具有自己的方法调用堆栈和程序计数器,使得线程在与其他线程并发地执行能够共享程序范围内的资源,比如共享内存,这种能力被称为多线程编程(multithreading),在核心的C和C++语言中并不具备这种能力,尽管他们影响了JAVA的设计. 2.   线程的生命周期 新线程的生命周期从"新生"状态开始.程序启动线程前,线程一直是"新生"状态:

  • JAVA多线程编程实例详解

    本文实例讲述了JAVA多线程编程.分享给大家供大家参考,具体如下: 进程是系统进行资源调度和分配的一个独立单位. 进程的特点 独立性:进程是系统中独立存在的实体,拥有自己的独立资源和私有空间.在没有经过进程本身允许的情况下,不能直接访问其他进程. 动态性:进程与程序的区别在于,前者是一个正在系统中活动的指令,而后者仅仅是一个静态的指令集合 并发性:多个进程可以在单个处理器上并发执行,而不受影响. 并发性和并行性的区别: 并行性:在同一时刻,有多条指令在多个处理器上同时执行(多个CPU) 并发性:

  • Python多线程编程入门详解

    目录 一.任务.进程和线程 任务 进程 线程 进程和线程的关系 二.Python既支持多进程,又支持多线程 Python实现多进程 Process进程类的说明 Python实现多线程 线程类Thread 总结 一.任务.进程和线程 现代操作系统比如Mac OS X, Linux,Windows等,都是支持"多任务"的操作系统. 什么叫"多任务"(multitasking)呢?简单地说,就是操作系统可以同时运行多个任务.例如你一边在用浏览器上查资料,一边在听MP3,一

  • Java多线程ForkJoinPool实例详解

    引言 java 7提供了另外一个很有用的线程池框架,Fork/Join框架 理论 Fork/Join框架主要有以下两个类组成. * ForkJoinPool 这个类实现了ExecutorService接口和工作窃取算法(Work-Stealing Algorithm).它管理工作者线程,并提供任务的状态信息,以及任务的执行信息 * ForkJoinTask 这个类是一个将在ForkJoinPool执行的任务的基类. Fork/Join框架提供了在一个任务里执行fork()和join()操作的机制

  • java多线程中断代码详解

    一.java中终止线程主要有三种方法: ①线程正常退出,即run()方法执行完毕了 ②使用Thread类中的stop()(已过期不推荐使用)方法强行终止线程. ③使用中断机制 t.stop()调用时,终止线程,会导致该线程所持有的锁被强制释放,从而被其他线程所持有,因此有可能导致与预期结果不一致.下面使用中断信号量中断非阻塞状态的线程中: public class TestStopThread { public static void main(String[] args) throws Int

  • Java并发编程之详解CyclicBarrier线程同步

    CyclicBarrier线程同步 java.util.concurrent.CyclicBarrier提供了一种多线程彼此等待的同步机制,可以把它理解成一个障碍,所有先到达这个障碍的线程都将将处于等待状态,直到所有线程都到达这个障碍处,所有线程才能继续执行. 举个例子:CyclicBarrier的同步方式有点像朋友们约好了去旅游,在景点入口处集合,这个景点入口就是一个Barrier障碍,等待大家都到了才一起进入景点游览参观. 进入景点后大家去爬山,有的人爬得快,有的人爬的慢,大家约好了山顶集合

  • Java并发编程之详解ConcurrentHashMap类

    前言 由于Java程序员常用的HashMap的操作方法不是同步的,所以在多线程环境下会导致存取操作数据不一致的问题,Map接口的另一个实现类Hashtable 虽然是线程安全的,但是在多线程下执行效率很低.为了解决这个问题,在java 1.5版本中引入了线程安全的集合类ConcurrentMap. java.util.concurrent.ConcurrentMap接口是Java集合类框架提供的线程安全的map,这意味着多线程同时访问它,不会影响map中每一条数据的一致性.ConcurrentM

  • Python使用Asyncio进行web编程方法详解

    目录 前言 什么是同步编程 什么是异步编程 ayncio 版 Hello 程序 如何使用 asyncio 总结 前言 许多 Web 应用依赖大量的 I/O (输入/输出) 操作,比如从网站上下载图片.视频等内容:进行网络聊天或者针对后台数据库进行多次查询.数据库查询可能会耗费大量时间,尤其是在该数据库处于高负载或查询很复杂的情况下. Web 服务器可能需要同时处理数百或数千个请求. I/O 是指计算机的输入和输出设备,例如键盘.硬盘驱动器,以及最常见的网卡.这些操作等待用户输入或从基于 Web

随机推荐