并发与多线程

news2025/7/20 8:22:35

目录

第一节 并发基本概念及实现,进程,线程基本概念

(1)并发,进程,线程的基本概念和综述

(1.1)并发

(1.2)可执行程序

(1.3)进程

(1.4)线程

(2)并发的实现方法

(2.1)多进程并发

(2.2)多线程并发

(2.3)总结

(3)C++11新标准线程库

第二节 线程启动,结束,创建线程方法,join,detach

(1)范例演示线程运行的开始和结束

(1.1)thread

(1.2)join()

(1.3)detach()

(1.4)joinable()

(2)其他创建线程的方法

(2.1)用类,以及一个问题范例

第三节 线程传参详解,detach大坑,成员函数做线程函数

(1)传递零临时对象作为线程参数

(1.1)要避免的陷阱

(2)临时对象作为线程参数继续讲

(2.1)线程id概念

(2.2)零时对象构造时间抓捕

(3)传递类对象,智能指针作为线程参数

(4)用成员函数真做线程函数

第四节 创建多个线程,数据贡献问题分析,案例代码

(1)创建和等待多个线程

(2)数据共享问题分析

(2.1)只有读的数据

(2.2)有读有写

(2.3)其他案例

(3)共享数据的保护案例代码

第五节 互斥量概念,用法,死锁演示及解决详解

(1)互斥量(mutex)的基本概念

(2)互斥量的用法

(2.1)lock(),unlock()

(2.2)lock_quard类模板

(3)死锁

(3.1)死锁的演示

(3.2)死锁的一般解决方案

(3.3)lock函数模板

(3.4)lock_quard的adopt_lockd参数

第六节 unique_lock

(1)unique_lock取代lock_guard

(2)unique_lock的第二个参数

(2.1)adopt_lock

(2.2)try_to_lock

(2.3)defer_lock

(3)unique_lock成员函数

(3.1)lock

(3.2)unlock

(3.3)try_lock

(3.4)release

(4)unique_lock所有权的传递



第一节 并发基本概念及实现,进程,线程基本概念

(1)并发,进程,线程的基本概念和综述

(1.1)并发

并发:两个或多个独立的任务同时发生,也就是一个程序需要同时执行多个独立的任务。

单核cpu:对于两个任务,计算机先执行a任务10ms,再去执行b任务10ms,再执行a任务10ms这种情况,且在切换过程中也会有消耗。但是,这种执行方式并不是真正的并发,也叫上下文切换

多核cpu:对于两个任务如果存在两个甚至3个cpu,那么就可以一个cpu处理一个任务。

实际开发中,往往cpu是少于任务数的,因此还是按单核处理方式,每个cpu不同时间处理不同任务,以达到效果。这样能够“同时”处理不同的事情,加快了处理时间。

(1.2)可执行程序

window:一个为exe结尾的文件

linux:具有rwxrwxrwx权限的文件

(1.3)进程

进程:一个可执行程序运行起来就是一个进程

(1.4)线程

每个进程都有一个主线程,可执行程序是通过主线程完成的,主线程和进程生死相依。

当创建了一个主线程时,可以人为加入线程,并且加入的线程可以执行不同的任务。

总结:

a.线程是用来执行代码的,一条线程代表执行的一条通路

b.一个进程包含一个主线程,可以人为额外加入线程

(2)并发的实现方法

a.多个进程实现并发

b.单个进程下多个线程并发

(2.1)多进程并发

也就是开启了两个程序,例如word和IE浏览器这种

(2.2)多线程并发

在一个进程中开了多个线程,线程之间共享内存,且对全局变量可以一起使用,所以使用多线程的资源消耗小于多进程。此外,共享内存也会存在数据一致性问题,例如线程A的数据会覆盖线程B

(2.3)总结

a.线程启动更快,更轻量级,消耗资源更少,速度更快

b.缺点是难度较大,要解决数据一致性问题

(3)C++11新标准线程库

之前创建线程的缺点;不能跨平台

window:CreateThread(),_beginThread(),_beginThreadxe()

linux:pthread_create()

现在:增加了C++语言对多线程的支持,具有可移植性

第二节 线程启动,结束,创建线程方法,join,detach

(1)范例演示线程运行的开始和结束

a)包含头文件《thread》

b)有一个子线程函数

c)main中开始执行代码

d)主线程和子线程是两个不同的线去执行,其中一个不会“阻挡”另外一个

一般情况下:想保持子线程运行就不能终止主线程。

// thread和join的使用

#include <iostream>
#include<thread>
using namespace std;

void myPrint() {
	cout << "子线程开始" << endl;

	cout << "子线程结束" << endl;
}

int main()
{
	thread myjob(myPrint);//创建了线程,且线程入口是myPrint()
	myjob.join();//阻塞主线程,让主线程等待子线程结束,然后子线程和主线程会和,然后再主线程

	cout << "这是主线程" << endl;

}

 

特殊情况下:主线程结束,子线程还在运行(主线程和进程不一样)


#include <iostream>
#include<thread>
using namespace std;

void myPrint() {
	cout << "子线程开始" << endl;

	cout << "子线程结束" << endl;
}

int main()
{
	thread myjob(myPrint);//创建了线程,且线程入口是myPrint()
	//myjob.join();//阻塞主线程,让主线程等待子线程结束,然后子线程和主线程会和,然后再主线程
	myjob.detach();//子线程被运行时库接管,由系统释放且被称为守护进程

	cout << "主线程结束" << endl;

}

 

 进程结束了,一切都结束了。

(1.1)thread

一个标准库的类,用来创建线程。当创建好后就会执行子线程了。

(1.2)join()

用来阻塞主线程,如果没有join就会导致子线程还没有结束,程序又去执行了主线程,然后程序又执行子线程这种错误输出情况。

(1.3)detach()

a)主线程和子线程各自运行,且主线程和子线程不再汇合,主线不用再等待子线程,主线程可以先结束,且不影响子线程。但也不能再人为管这个子线程了

b)存在detach是因为子线程太多了,让主线程等不划算。

c)一但调用detach就不能调用join

(1.4)joinable()

判断是否可以成功使用join和detach,返回true或者false

	if (myjob.joinable()) {
		cout << "可以join" << endl;
	}
	else {
		cout << "不可以join" << endl;
	}

(2)其他创建线程的方法

(2.1)用类,以及一个问题范例

#include <iostream>
#include<thread>
using namespace std;

class TA {
public:
	void operator()() {
		cout << "子线程开始" << endl;

		cout << "子线程结束" << endl;
	}
};

int main()
{
	TA ta;
	thread myjob(ta);//ta对象被复制到了线程,且线程入口是ta对象()
	myjob.join();//阻塞主线程,让主线程等待子线程结束,然后子线程和主线程会和,然后再主线程
	cout << "主线程结束" << endl;

	return 0;
}

第三节 线程传参详解,detach大坑,成员函数做线程函数

(1)传递零临时对象作为线程参数

#include <iostream>
#include<thread>
using namespace std;

//void myPrint(const int &i, char* pmybuf) {
//
//	cout <<i<< endl;//i并不是传入值的引用,而是复制了一个值的引用,是值传递(引用)
//	cout << pmybuf<< endl;//不是值传递,如果main进程结束,则会出问题(指针)
//	return;
//}
void myPrint(const int& i, const string &pmybuf) {

	cout << i << endl;//i并不是传入值的引用,而是复制了一个值的引用,是值传递(引用)
	cout << pmybuf.c_str() << endl;//不是值传递,如果main进程结束,则会出问题(指针)
	return;
}



int main()
{
	//避免的陷阱

	int mvar = 1;
	int& mvary = mvar;
	char mybuf[] = "this is 子线程" ;
	thread myjob(myPrint,mvary,mybuf);//创建了线程,且线程入口是ta对象()
	myjob.join();//阻塞主线程,让主线程等待子线程结束,然后子线程和主线程会和,然后再主线程
	cout << "主线程结束" << endl;

	return 0;
}

(1.1)要避免的陷阱

a)使用detach时不能有引用和指针,因为当进程有可能先结束,内存被回收了,那么线程会出问题

b)引用:可能出现问题

c)指针:一定出现问题(除非用string接)

d)解决办法,对于数组,可以直接在字符数组调用前利用string转换,也就是利用临时对象

(2)临时对象作为线程参数继续讲

(2.1)线程id概念

a)主线程和子线程都对应一个id且不相同。

b)可以通过get_id()获取

(2.2)零时对象构造时间抓捕

(3)传递类对象,智能指针作为线程参数

(4)用成员函数真做线程函数

第四节 创建多个线程,数据贡献问题分析,案例代码

(1)创建和等待多个线程

// ConsoleApplication1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include<thread>
#include<vector>
using namespace std;

//void myPrint(const int &i, char* pmybuf) {
//
//	cout <<i<< endl;//i并不是传入值的引用,而是复制了一个值的引用,是值传递(引用)
//	cout << pmybuf<< endl;//不是值传递,如果main进程结束,则会出问题(指针)
//	return;
//}
void myPrint(int num) {

	cout << "线程开始,线程编号= " <<num<< endl;
	cout << "线程结束,线程编号= " <<num << endl;
	return;
}



int main()
{
	vector<thread> mythread;

	cout << "主线程结束" << endl;
	for (int i = 0; i < 10; i++) {
		mythread.push_back(thread(myPrint, i));//创建10个线程,且开始执行
	}
	for (auto it = mythread.begin(); it != mythread.end(); it++) {
		it->join();//等待10个线程返回
	}
	cout << "主线程结束" << endl;
	return 0;
}

a)多个线程执行顺序是乱的,这和操作系统本身资源调度有关系 

(2)数据共享问题分析

(2.1)只有读的数据

共享数据:每个线程都可以读

// ConsoleApplication1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include<thread>
#include<vector>
using namespace std;

vector<int>vec = { 1,2,3 };

void myPrint(int num) {

	cout << "id= "<<this_thread::get_id <<"的线程打印gv的值" << vec[0]<<vec[1]<<vec[2] << endl;

	return;
}

int main()
{
	vector<thread> mythread;


	for (int i = 0; i < 10; i++) {
		mythread.push_back(thread(myPrint, i));//创建10个线程,且开始执行
	}
	for (auto it = mythread.begin(); it != mythread.end(); it++) {
		it->join();//等待10个线程返回
	}
	cout << "主线程结束" << endl;
	return 0;
}

 虽然线程输出时间不一样,但是每个线程获取的值一样 

(2.2)有读有写

简单办法:读的时候不能写,写的时候不能读,并且写的读和写的线程也不能单独一起执行,只能单个执行

(2.3)其他案例

比如火车订票,一张票不能同时给两个人

(3)共享数据的保护案例代码

网络游戏服务器。一个线程收集玩家命令,存入队列中

                             一个线程从队列中取出玩家命令,并执行

关共享数据中的互斥量用来保护

第五节 互斥量概念,用法,死锁演示及解决详解

(1)互斥量(mutex)的基本概念

a)为了保护共享数据,用代码把共享数据锁住,其他想操作共享数据的必须等待。

b)互斥量是一个类对象。可以理解为一把锁,线程想要使用这个数据,就要给这个数据上锁,如果上锁成功也就是lock返回了,否则无法上锁则无法使用该数据

c)互斥量保护数据要小心,保护数据过多导致效率降低,少了则没有效果

(2)互斥量的用法

(2.1)lock(),unlock()

a)步骤:lock,操作共享数据,unlock

b)lock和unlock必须成对使用



#include <iostream>
#include<thread>
#include<vector>
#include<list>
#include<mutex>

using namespace std;

vector<int>vec = { 1,2,3 };

void myPrint(int num) {

	cout << "id= "<<this_thread::get_id <<"的线程打印gv的值" << vec[0]<<vec[1]<<vec[2] << endl;

	return;
}
class A {
public:
	//把收到的玩家命令存入队列
	void inMsgRecQueue() {
		for (int i = 0; i < 10000; ++i) {
			cout << "inMsgRecQueue执行,插入第一个元素" << i << endl;
			//开启保护
			my_mutex.lock();
			msgRecQueue.push_back(i);//i代表玩家命令,插入消息队列
			//关闭保护
			my_mutex.unlock();
		}
		return;
	}
	bool OutMsgLULProc(int &command)
	{
		my_mutex.lock();
		if (!msgRecQueue.empty()) {
			//消息不为空,取出数据
			int command = msgRecQueue.front();//返回第一个元素
			msgRecQueue.pop_front();//移除队列第一个元素
			//根据命令进行处理
			my_mutex.unlock();
			return true;

		}
		my_mutex.unlock();
		return false;
	}
	//把数据从队列中取出
	void outMsgRecQueue() {
		int command = 0;
		for (int i = 0; i < 10000; ++i) {
			bool result = OutMsgLULProc(command);
			if (result == true) {
				cout << "outMsgRecQueue执行,取出一个元素:" << command << endl;
			}
			else {
				//此时虽然执行了outMsgRecQueue,但是队列中没有消息,该线程只能等待
				cout << "outMsgRecQueue执行,但目前消息队列中为空" << i << endl;
			}
		}
		cout << "end" << endl;
	}

private:
	list<int>msgRecQueue;
	mutex my_mutex;

};

int main()
{
	A myjob;
	thread myOutMsgObj(&A::outMsgRecQueue,&myjob);
	thread myInMsgObj(&A::inMsgRecQueue, &myjob);
	myInMsgObj.join();
	myOutMsgObj.join();
	
	多线程执行
	//vector<thread> mythread;
	//for (int i = 0; i < 10; i++) {
	//	mythread.push_back(thread(myPrint, i));//创建10个线程,且开始执行
	//}
	//for (auto it = mythread.begin(); it != mythread.end(); it++) {
	//	it->join();//等待10个线程返回
	//}
	//cout << "主线程结束" << endl;

	return 0;
}

(2.2)lock_quard类模板

a)程序员忘记umlock,lock_quard帮你unlock,类似智能指针,忘记释放不要紧,给你释放

b)lock_guard直接取代lock和unlock,你用了lock_guard之后不能用lock和unlock

c)lock_guard的构造函数执行了lock

d)lock_guard的析构函数执行了unlock

e)可以通过{}来决定构造和析构的位置

	bool OutMsgLULProc(int &command)
	{
		lock_guard<mutex> sbguard(my_mutex);
		//my_mutex.lock();
		if (!msgRecQueue.empty()) {
			//消息不为空,取出数据
			int command = msgRecQueue.front();//返回第一个元素
			msgRecQueue.pop_front();//移除队列第一个元素
			//根据命令进行处理
			//my_mutex.unlock();
			return true;

		}
		//my_mutex.unlock();
		return false;
	}

(3)死锁

a)你不录取我,我一直等你,你不来我公司,我一直等你

b)一个互斥量是一把锁。死锁必须至少有两把锁(两个互斥量)

c)在C++中,线程A需要先lock锁1,再lock锁2.线程B需要先lock锁2,再lock锁1.当Alock了锁1准备lock锁2时,发生了上下文切换此时Block了锁2。当B准备lock锁1时就发生了死锁,因为B不能lock锁1,此时锁1正被Alock了

(3.1)死锁的演示

lock有顺序,unlock没有顺序

class A {
public:
	//把收到的玩家命令存入队列
	void inMsgRecQueue() {
		for (int i = 0; i < 10000; ++i) {
			cout << "inMsgRecQueue执行,插入第一个元素" << i << endl;
			//开启保护
			my_mutex1.lock();
			my_mutex2.lock();
			msgRecQueue.push_back(i);//i代表玩家命令,插入消息队列
			//关闭保护
			my_mutex2.unlock();
			my_mutex1.unlock();
		}
		return;
	}
	bool OutMsgLULProc(int &command)
	{
		//lock_guard<mutex> sbguard(my_mutex);
		my_mutex2.lock();
		my_mutex1.lock();
		if (!msgRecQueue.empty()) {
			//消息不为空,取出数据
			int command = msgRecQueue.front();//返回第一个元素
			msgRecQueue.pop_front();//移除队列第一个元素
			//根据命令进行处理
			//my_mutex.unlock();
			my_mutex2.unlock();
			my_mutex1.unlock();
			return true;

		}
		//my_mutex.unlock();
		my_mutex2.unlock();
		my_mutex1.unlock();
		return false;
	}

(3.2)死锁的一般解决方案

a)保证两个互斥量上锁顺序一致

b)lock_guard顺序一致也可以

(3.3)lock函数模板

a)能力:一次性可以锁住多个互斥量,不限互斥量的数量,互斥量顺序无所谓。

b)不会存在因为锁的顺序问题导致死锁,因为其实现本质是如果lock了锁1准备lock锁2出现问题,则直接unlock了锁1。

c)如果只剩一个没lock,它就会等着

lock(my_mutex2, my_mutex1);

(3.4)lock_quard的adopt_lockd参数

    lock(my_mutex1, my_mutex2);
	lock_guard<mutex> sbguard1(my_mutex1, adopt_lock);
	lock_guard<mutex> sbguard2(my_mutex2, adopt_lock);

adopt_lock作用表示这个互斥量已经lock了,不需要再通过lock_guard的构造函数lock

第六节 unique_lock

是一个类模板

(1)unique_lock取代lock_guard

比lock_guard灵活,但效率差,内存消耗多

(2)unique_lock的第二个参数

(2.1)adopt_lock

a)adopt_lock作用表示这个互斥量已经lock了,不需要再通过lock_guard的构造函数lock,换句话说,使用这个参数你必须提起lock

b)使用方法和lock_guard一样

my_mutex1.lock();
unique_lock<mutex> sbguard1(my_mutex1,adopt_lock);

(2.2)try_to_lock

a)尝试去lock

b)不能先lock,如果有了lock再try_to_lock会卡死

unique_lock<mutex> sbguard1(my_mutex1, try_to_lock);
if (sbguard1.owns_lock()) {
	//成功拿到锁
		msgRecQueue.push_back(i);//i代表玩家命令,插入消息队列
	}
else {
	//没拿到锁
	cout << "msgRecQueue没拿到锁" << endl;
}

(2.3)defer_lock

不能自己先lock,否则会报异常。defer_lock代表初始化了一个没有加锁的mutex

(3)unique_lock成员函数

(3.1)lock

(3.2)unlock

(3.3)try_lock

(3.4)release

(4)unique_lock所有权的传递


暂停更新!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/369126.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

物理服务器与云服务器备份相同吗?

自从云计算兴起以来&#xff0c;服务器备份已经从两阶段的模拟操作演变为由云服务器备份软件执行的复杂的多个过程。但是支持物理服务器和虚拟服务器之间的备份相同吗?主要区别是什么?我们接下来将详细讨论这个问题。 物理服务器与云服务器备份的区别 如果您不熟悉虚拟服务器…

qt QCustomPlot学习

QCustomPlot 是一个基于Qt的画图和数据可视化C控件。QCustomPlot 致力于提供美观的界面&#xff0c;高质量的2D画图、图画和图表&#xff0c;同时为实时数据可视化应用提供良好的解决方案。 该绘图库专注于制作美观、出版物质量高的2D绘图、图形和图表&#xff0c;并为实时可视…

数据库专题

请简洁描述 MySQL 中 InnoDB 支持的四种事务隔离级别名称&#xff0c;以及逐级之间的区别&#xff1f; 默认隔离级别 mysql repeatable-read oracle read-committed 脏读&#xff1a;不可重复读&#xff1a;幻读&#xff1a; CHAR 和 VARCHAR 的区别&#xff1f;…

公众号运营之竞品分析,教你拆解公众号

知己知彼&#xff0c;百战不殆&#xff0c;公众号运营亦是如此。 当运营者只关注自己账号的时候&#xff0c;很容易陷入某个误区中出不来。这个时候就要拓宽我们的视野&#xff0c;多去看看“外面的世界”&#xff0c;不要只局限于自己的一片小天地中。 看看同领域优秀公众号…

stm32f407探索者开发板(二十二)——通用定时器基本原理讲解

文章目录一、三种定时器的区别二、通用定时器特点2.1 功能特点描述2.2 计数器模式三、通用定时器工作过程四、附一、三种定时器的区别 STM32F40x系列总共最多有14个定时器 三种&#xff08;4&#xff09;STM32定时器区别 二、通用定时器特点 2.1 功能特点描述 STM3 F4的通…

PHY设备驱动

1. 概述 MAC控制器的驱动使用的是platform总线的连接方式&#xff0c;PHY设备驱动是基于device、driver、bus的连接方式。 其驱动涉及如下几个重要部分&#xff1a; 总线 - sturct mii_bus (mii stand for media independent interface) 设备 - struct phy_device 驱动 - struc…

零日漏洞发展格局及防御策略

在过去的一年半中&#xff0c; 在野利用的零日漏洞数量持续飙升 &#xff0c;这些软件制造商尚不知晓的漏洞正在被国家行为体黑客组织和勒索软件团伙滥用。 今年上半年&#xff0c;Google Project Zero统计了近20个零日漏洞&#xff0c;其中 大部分针对微软、苹果和谷歌构建的…

【《C Primer Plus》读书笔记】第13章:文件输入/输出

【《C Primer Plus》读书笔记】第13章&#xff1a;文件输入/输出13.1 与文件进行通信13.1.1 文件是什么13.1.2 文本模式和二进制模式13.1.3 I/O的级别13.1.4 标准文件13.2 标准I/O13.3 一个简单的文件压缩程序13.4 文件I/O&#xff1a;fprintf()、fscanf()、fgets()和fputs()13…

【LVGL】学习笔记--(1)Keil中嵌入式系统移植LVGL

一 LVGL简介最近emwin用的比较烦躁&#xff0c;同时被LVGL酷炫的界面吸引到了&#xff0c;所以准备换用LVGL试试水。LVGL(轻量级和通用图形库)是一个免费和开源的图形库&#xff0c;它提供了创建嵌入式GUI所需的一切&#xff0c;具有易于使用的图形元素&#xff0c;美丽的视觉效…

极光笔记 | 埋点体系建设与实施方法论

PART 01 前 言随着网络技术的发展&#xff0c;从粗犷型到精细化运营型&#xff0c;再到现在的数字化运营&#xff0c;数据变得越来越细分和重要&#xff0c;不仅可以进行策略调整&#xff0c;还可以实现自动化的精细化运营。而数据价值的起点就是埋点&#xff0c;只有合理地埋点…

[计算机网络(第八版)]第一章 概述(学习笔记)

1.1 计算机网络在信息时代中的作用 21世纪是以网络为核心的信息时代&#xff0c;21世纪的重要重要特征&#xff1a;数字化、网络化与信息化。 三大类网络 电信网络&#xff1a;向用户提供电话、电报、传真等服务&#xff1b;有线电视网络&#xff1a;向用户传送各种电视节目&am…

zabbix4.0-使用zabbix监控别的主机-使用模板来创建图形

目录 1、 配置zabbix的yum源 2、下载zabbix-agent 3、配置zabbix-agent的配置文件 4、关闭防火墙&#xff0c;selinux 5、重启zabbix-agent 6、连通性测试&#xff0c;在zabbix-server服务器上面使用zabbix_get获取zabbix-agent服务器上的数据 7、在zabbix web端配置zab…

Windows 11 22H2 中文版、英文版 (x64、ARM64) 下载 (updated Feb 2023)

Windows 11, version 22H2&#xff0c;2023 年 2 月 更新 请访问原文链接&#xff1a;https://sysin.org/blog/windows-11/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;www.sysin.org 全新推出 Windows 11 全新 Windows 体验&#x…

考PMP的用处有哪些?备考攻略+资料分享

说到底&#xff0c;考PMP就是为了给工作提供便利&#xff0c;你考了之后会用它&#xff0c;将学习的东西运用到工作中&#xff0c;比如提高项目完成率&#xff0c;能升职加薪啊等等&#xff0c;那自然很是有用的。 不用&#xff0c;那就只是一张当摆设的纸&#xff0c;当然&am…

项目经理如何度量项目?及项目度量指标实例【静说】

度量项目是项目经理的一个重要职责&#xff0c;通过度量项目&#xff0c;项目经理可以了解项目的进展情况&#xff0c;及时发现问题并采取相应的措施&#xff0c;以确保项目能够按时、按质、按预算完成。 分享给大家一些常见的项目度量指标&#xff1a; 1. 项目进度&#xff…

docker-compose 简单配置php和nginx及注意事项

docker-compose.yml内容&#xff1a; /docker/web/config/nginx/conf/default.conf内容&#xff1a; server { listen 80; server_name localhost; root /usr/share/nginx/html; error_log /var/log/nginx/localhost.log; location / { try_files $…

ESP32-FPV-Camera介绍和使用

ESP32-FPV-Camera介绍和使用1. 编译目标2. 编译步骤Step 1 软件配置环境准备Step 2 获取开源代码Step 3 2.4G WiFi频段选择Step 4 要确保2.4G WiFi网卡处于Monitor状态Step 5 修改频点相关代码Step 6 修改WiFi网卡相关代码Step 7 OpenGL 版本问题Step 8 构建天空端Step 9 构建地…

从0开始写Vue项目-Vue实现用户数据批量上传和数据导出

从0开始写Vue项目-环境和项目搭建_慕言要努力的博客-CSDN博客从0开始写Vue项目-Vue2集成Element-ui和后台主体框架搭建_慕言要努力的博客-CSDN博客从0开始写Vue项目-Vue页面主体布局和登录、注册页面_慕言要努力的博客-CSDN博客从0开始写Vue项目-SpringBoot整合Mybatis-plus实现…

04--WXML

1、什么是WXML什么是Wxml呢&#xff1f;我们首先要介绍一下Html&#xff0c;Html的全称为HyperTextMarkup Language&#xff0c;翻译过来就是超文本标记语言&#xff0c;这种语言目前已经普遍用于前端开发&#xff0c;而wxml正是从html演变而来&#xff0c;它基于微信这个平台&…

4EVERLAND:ERC-721 Token的存储选择

4EVERLAND&#xff1a;一个 Web3 基础设施&#xff0c;可促进项目更轻松、更快速地托管前端、存储数据/NFT/文件&#xff0c;并在 IPFS、Arweave 和 Dfinity 之上访问它们。 NFT , 数字所有权 使用以太坊标准的 NFT 创新ERC-721解决了互联网内容的主要问题之一&#xff1a;所…