当前位置: 首页 > news >正文

从0开始linux(39)——线程(2)线程控制

欢迎来到博主的专栏:从0开始linux
博主ID:代码小豪

文章目录

    • 线程创建
      • 线程标识符
      • 线程参数
      • 多线程竞争资源
    • 回收线程
      • detach
    • 线程退出
      • pthread_cancel

线程创建

线程创建的函数为pthread_create。该函数是包含在posix线程库当中,posix线程是C语言处理线程的一个标准接口,我们的进程创建、杀死和回收线程,都可以通过posix库来完成完成,这些函数包含在<pthread.h>库函数当中,且大部分函数的名字都是以pthread开头,但是要注意该库不是C语言标准库,因此在使用<pthread.h>时,要记得gcc/g++的编译选项当中加上-lpthread

pthread_create函数原型如下:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

在我们上一篇文章当中就使用了pthread_create函数,但是博主当时并没有介绍完全这些参数,接下来博主会使用三个pthread_create的例子,让大家了解这些参数到底有什么用。

  • thread:输出型参数,创建一个pthread_t类型的变量,并将其地址传入函数当中,可以获得创建的线程的标识符
  • attr:设置线程的属性,使用NULL表示线程设置成默认模式,博主在本篇的例子中创建的都是默认模式的线程
  • start_routine:创建的线程的入口函数,类似于线程自己的main函数
  • arg:传入给新线程的参数

线程标识符

在我们上一篇文章中,我们提到linux将线程描述成一个轻量级进程(LWP),而且通过ps -aL可以查到轻量级进程的LWP号,那么LWP号和线程标识符thread有什么关系呢?

我们话不多说,直接上例子。

void* routine1(void* arg)//新线程入口函数
{while(true){::sleep(1);}
}int main()
{pthread_t tid1;//线程标识符pthread_create(&tid1,nullptr,routine1,nullptr);//创建新线程std::cout<<"thread1 tid:"<<tid1<<std::endl;while(true){sleep(1);}return 0;
}

接下来我们运行程序,打开另外一个终端,输入指令:ps -aL | head -1;ps -aL | grep thread

在这里插入图片描述
在这里插入图片描述
这很明显了,线程标识符和lwd号根本不是一个东西,但是它们指向的都是同一个线程,那么为什么要这样呢?首先我们搞清楚一个点,那就是<pthread.h>库是C语言封装的库,不是操作系统的系统调用库,因此C语言不必与操作系统在线程描述方面统一起来,因此lwp号是操作系统用于辨识轻量级进程的,而tid是C语言封装用来辨识线程的,虽然在linux当中lwp和线程是同一个东西。但是不妨碍C语言将其视为其他属性。

线程参数

相信大家对pthread_create的参数有点疑惑吧?第一个就是为什么我们写的start_routine(线程的入口地址)是要返回值为void*,参数为void的回调函数?返回值为void就算了,那个void的参数到底有什么用啊,我们平时创建出线程之后,线程自己就跳转到回调函数当中去了,那里轮得到我们自己使用。那么那个void的参数是什么,怎么传,别急,听我娓娓道来。

在pthread_create当中存在一个void的参数也很存疑。即void *arg,这个arg能传一个void类型的指针,这个指针其实就是给回调函数start_routine的参数,由于它是一个void*的指针,因此我们可以用它指向任何数据,整形、浮点型、任何的c/c++的内置类型,甚至可以是结构体,类的对象。

我们简简单单的定义一个类:

class thread
{
public:thread(std::string name,int a,int b):_name(name),_a(a),_b(b){;}void Excute(){std::cout<<_name<<'-'<<_a<<'-'<<_b<<std::endl;}private:std::string _name;int _a;int _b;
};

接下来我们在main函数当中创建指向该类的对象的指针,并且将该指针转为void*类型。作为参数传递给pthread_create。

int main()
{pthread_t tid;thread* objptr=new thread("thread-1",11,26);pthread_create(&tid,nullptr,routine1,(void*)objptr);while(true){sleep(1);}return 0;
}

而我们的routine1函数的参数不是要求是void类型的吗?实际上该参数就是我们传入的objptr,在routine1函数当中,我们可以将void的指针转换成对象类型的指针thread*。这样我们就可以在线程执行的函数当中,传入数据了。

void* routine1(void*arg)
{thread* obj=static_cast<thread*>(arg);//将void*类型转换成thread*obj->Excute();return nullptr;
}

接下来我们编译并执行该程序。
在这里插入图片描述
可以发现,线程入口函数的参数,其实就是我们传入给pthread_create的void*参数arg。

多线程竞争资源

线程之间的数据和代码是共享的,这意味着我们在运行多线程的进程时,会很难控制它们的运行情况。我们可以创建多个线程,让他们共同对一个全局变量进行修改,并且使用同一个函数进行打印,看看会发生什么。

int shared_arg=100;//共同使用的全局变量
void* sharedroutine(void* arg)//共同使用的函数
{std::string name(static_cast<char*>(arg));while(true){shared_arg++;std::cout<<"tid:"<<name<<"   arg:"<<shared_arg<<std::endl;::usleep(10);}
}int main()
{pthread_t tid1;//线程1标识符pthread_t tid2;//线程1标识符pthread_t tid3;//线程1标识符pthread_t tid4;//线程1标识符pthread_create(&tid1,nullptr,sharedroutine,(void*)"thread-1");//创建新线程1pthread_create(&tid2,nullptr,sharedroutine,(void*)"thread-2");//创建新线程2pthread_create(&tid3,nullptr,sharedroutine,(void*)"thread-3");//创建新线程3pthread_create(&tid4,nullptr,sharedroutine,(void*)"thread-4");//创建新线程4swhile(true){sleep(1);}return 0;
}

我们编译并执行程序。
在这里插入图片描述
可以发现,定义在全局当中变量shared_arg在所有的线程当中是共享的,线程1对其修改后,线程2、线程3、线程4的shared_arg也会跟着修改。那么此时有人就问了,局部变量就不共享吗?实际上也是共享的,只是局部变量会存在于栈区间上,而线程之间的栈区间是独立的,除非你能让线程找到其他线程栈区间上的变量。但是博主想不到该怎么做(haha)。

而且我们发现线程之间既然发生了写入重复的错误,那么这是如何导致的呢?我们了解过,线程会共享进程中的资源,其中就包括文件资源,而linux当中一切皆文件,包括显示器,因此如果我们向显示器写入数据,如果线程1和线程2同时都在向显示器写入数据,都会发现这种写入重复的情况。实际上不仅仅是显示器,只要是多个线程向任何一种文件写入数据,都有可能发生这种情况。

那么我们可以看到,线程对于公开资源的使用其实是需要我们控制的,因为你也不想发现程序执行完后,明明逻辑写的对的,但是一打开文件、发现写的数据都是乱七八糟的吧?而线程的控制方法,就是线程的互斥和同步,这一点我们后面再说。

回收线程

由于线程有其独立的内核数据结构,而内核数据结构是实打实存在于内存当中的,因此如果当线程结束后,没有对线程进行回收,这个内核数据结构就会一直存在于内存当中,造成内存泄漏,这个道理和进程回收很像。

那么为什么线程不会自动回收呢?这是因为我们的线程在结束之后是会有返回值的,这个返回值可以被用户所使用,因此操作系统不敢自动回收线程,因为操作系统也不确定我们用户到底用不用这个返回值,也不确定我们何时用线程的返回值。因此操作系统就只好一直帮助我们保存。直到用户主动回收为止。

那么线程回收的函数是什么?

int pthread_join(pthread_t thread, void **retval);
  • thread:pthread_t类型的参数,即我们想要回收的线程的线程描述符
  • retval:输出型参数,还记的我们线程的入口函数,其返回值是void*的吧?这个retval就是接收线程的返回值的。

如果pthread_join正常回收,其返回值为0,若pthread_join回收异常,则返回值为非0。但是博主在linux环境下没有找到引起pthread_join回收失败返回值为非0的情况。因为大部分时候如果线程出现了错误,进程就直接被杀死了。那么博主就不试了。

要注意,pthread_join会引起进程阻塞,而且线程不能自己回收自己,否则会造成deadlock(死锁),因为线程回收自己,会导致阻塞,而线程自己阻塞了,就永远无法结束,也就无法回收了,不能回收又会一直阻塞……(俄罗斯套娃)

关于回收,我们先简单认识这个函数,线程到底如何回收会更好?这一点我们后面再说。

话不多说,直接上例子。

void* routine1(void*arg)
{thread* obj=static_cast<thread*>(arg);//将void*类型转换成thread*obj->Excute();return (void*)10;
}

我们接着用上面使用过的代码,但是将其改造了一下,routine1的返回值从nulllptr变成了arg。

int main()
{pthread_t tid;thread* objptr=new thread("thread-1",11,26);pthread_create(&tid,nullptr,routine1,(void*)objptr);void* threadret=nullptr;int n=pthread_join(tid,&threadret);if(n==0) std::cout<<"回收tid:"<<tid<<"成功"<<std::endl;unsigned long ret=(unsigned long)threadret;std::cout<<ret<<std::endl;return 0;
}

在主线程当中,我们用void**类型的参数接收routine1函数的返回值,并且将其转换成unsigned long类型的变量。由于我们将其以void*类型返回了,因此要转换成unsigned long之后,才能看到返回值10。

接下来我们编译并运行。
在这里插入图片描述

可以发现确实能将线程回收,而且routine1当中的返回值确实是能通过pthread_join获得。

通常情况下,回收线程的工作都是交给主线程来完成,如果主线程结束,那么其他线就会继续运行,因此大部分情况下,主线程一定会比其他线程晚退出。

detach

由于主线程在回收线程时,需要阻塞等待待回收的线程结束,那么如果线程直接没有结束,那么主线程就会一直等待,这可能不是你想要的代码逻辑,那么有没有办法让主线程等待线程时,不阻塞等待。

线程有两种状态,其中一种是joined,也是我们创建新线程的默认状态,还有一种叫做detach。我们可以使用函数pthread_detach使线程进入detach状态,当线程处于detach状态下,对于该线程的回收操作将会失效。

int pthread_detach(pthread_t thread);

这个函数就不展示用例,我们向pthread_detach函数,传入线程标识符thread,thread就会进入detach状态。处于detach状态的线程,会在线程退出后,自动被操作系统回收。如果我们尝试用pthread_join回收detach状态下的线程,那么pthread_join只会返回一个非0的值,说明回收线程的操作失败了。detach的线程是不会被其他线程回收的。

线程退出

线程的退出方法有两种,一种是直接在线程的执行函数当中return,这个方法我们一直在前面的例子当中使用,因此不多bb。

第二种方法是在线程的执行函数使用pthread_exit函数。该函数原型如下:

void pthread_exit(void *retval);
  • retval:线程结束的返回值,在主线程当中可以使用pthread_joined接收到该返回值。

我们可以在线程的任意一个执行位置使用pthread_exit。当线程执行到该位置时,就会直接退出,这是和return结束线程不一样的地方,pthread_exit可以在任意的位置退出,即使线程处于调用中的函数。

void thread_to_do()
{std::cout<<"线程正在执行thread_todo"<<std::endl;//如果真推出了,显示器打印的信息只有这个pthread_exit((void*)10);
}void* routine1(void*arg)
{thread_to_do();std::cout<<"线程从thread_todo执行回来"<<std::endl;//如果没有退出,那么还会打印这个信息return (void*)10;
}

我们让线程在执行thread_to_do函数,接着在thread_to_do函数当中调用pthread_exit函数,如果真如我们所言,pthread_t能让线程无论在执行任何函数时,都能无视函数栈帧直接退出,那么屏幕上只会打印出“线程正在执行……”。而不会打印后面的信息。
在这里插入图片描述

那么有人可能会这么觉得,我们使用exit()函数是不是也能让线程退出呢?这句话对,但也不对,对是因为当线程执行到exit时,确实会退出,但是不对的地方在于,不仅仅执行exit()函数的线程会退出,进程中所有的一切线程,也会随之退出。因此exit其实是不能作为线程退出来使用的,因为它本身的作用是让进程退出,而非线程。因此在程序当中使用exit函数时,一定要注意你退出的要是进程还是线程。

pthread_cancel

严格来说,pthread_cancel函数并不属于线程退出,而是将一个特定的线程直接删除,被删除的线程需要被回收,一般这个工作都是让主线程来完成。我们先来看看pthread_cancel的函数原型。

int pthread_cancel(pthread_t thread);
  • thread:这是一个pthread_t类型的参数,即线程标识符,删除thread对应线程标识符的线程。

这个功能很简单,但是我们有一个问题是要解决的,那就是被删除的线程不是要回收吗?那么回收不就是能接收到线程的返回值,那么它的返回值是多少呢?

void* routine1(void*arg)
{thread* obj=static_cast<thread*>(arg);//将void*类型转换成thread*while(true){obj->Excute();sleep(1);}return (void*)10;
}int main()
{pthread_t tid;thread* objptr=new thread("thread-1",11,26);pthread_create(&tid,nullptr,routine1,(void*)objptr);sleep(1);pthread_cancel(tid);//将tid对应的线程删除sleep(5);void* threadret=nullptr;int n=pthread_join(tid,&threadret);if(n==0) std::cout<<"回收tid:"<<tid<<"成功"<<std::endl;long ret=(long)threadret;std::cout<<ret<<std::endl;return 0;
}

运行结果为:
在这里插入图片描述
pthread_cancel结束的进程,其返回值为PTHREAD_CANCELED;这是一个宏,我们可以找到这个宏定义。
在这里插入图片描述
也就是说,只要被pthread_cancel结束的线程,其返回值只会是-1。

相关文章:

从0开始linux(39)——线程(2)线程控制

欢迎来到博主的专栏&#xff1a;从0开始linux 博主ID&#xff1a;代码小豪 文章目录 线程创建线程标识符线程参数多线程竞争资源 回收线程detach 线程退出pthread_cancel 线程创建 线程创建的函数为pthread_create。该函数是包含在posix线程库当中&#xff0c;posix线程是C语言…...

International Journal of Medical Informatics投稿经历时间节点

20240423&#xff0c;完成投稿 20240612&#xff0c;按编辑要求修改后再投, with editor 20240613&#xff0c;under review&#xff0c;completed 0, accepted 0, invitation 2. 20240620, under review&#xff0c;completed 0, accepted 1, invitation 2. 20240626, unde…...

BUUCTF—Reverse—Java逆向解密(10)

程序员小张不小心弄丢了加密文件用的秘钥&#xff0c;已知还好小张曾经编写了一个秘钥验证算法&#xff0c;聪明的你能帮小张找到秘钥吗&#xff1f; 注意&#xff1a;得到的 flag 请包上 flag{} 提交 需要用专门的Java反编译软件:jd-gui 下载文件&#xff0c;发现是个class文…...

CLIP-MMA: Multi-Modal Adapter for Vision-Language Models

当前的问题 CLIP-Adapter仅单独调整图像和文本嵌入&#xff0c;忽略了不同模态之间的交互作用。此外&#xff0c;适应性参数容易过拟合训练数据&#xff0c;导致新任务泛化能力的损失。 动机 图1所示。多模态适配器说明。 通过一种基于注意力的 Adapter &#xff0c;作者称之…...

三维扫描仪-3d扫描建模设备自动检测尺寸

在现代工业制造领域&#xff0c;三维扫描仪已成为实现高精度尺寸检测的关键设备。CASAIM自动化智能检测系统以其自动化三维立体扫描技术&#xff0c;为产品尺寸的自动检测提供了高效、可靠的解决方案。 CASAIM自动化智能检测系统通过非接触式测量方式&#xff0c;通过激光扫描…...

vue3+ant design vue实现日期选择器默认显示当前年,并限制用户只能选择当前年及之前~

1、思路&#xff1a;之前想拿当前年直接做赋值操作&#xff0c;实际上是行不通的&#xff0c;因为组件本身有数据格式限制&#xff0c;会出现报错&#xff0c;然后索性直接获取当前日期&#xff08;YYYY-MM-DD&#xff09;赋值给日期组件&#xff0c;这样不管你用的是年&#x…...

【electron-vite】搭建electron+vue3框架基础

一、拉取项目 electron-vite 中文文档地址&#xff1a; https://cn-evite.netlify.app/guide/ 官网网址&#xff1a;https://evite.netlify.app/ 版本 vue版本&#xff1a;vue3 构建工具&#xff1a;vite 框架类型&#xff1a;Electron JS语法&#xff1a;TypeScript &…...

05《存储器层次结构与接口》计算机组成与体系结构 系列课

目录 存储器层次结构概述 层次结构的定义 存储器的排名 存储器接口 处理器与存储器的速度匹配 存储器接口的定义 存储器访问命中率 两种接口 第1种方式&#xff1a;并行 命中率的计算 存储器访问时间 第2种方式&#xff1a;逐级 结语 大家好&#xff0c;欢迎回来。…...

elasticsearch报错fully-formed single-node cluster with cluster UUID

1.问题描述 k8s集群内部署的es中间件起不来&#xff0c;查看日志发现如下警告&#xff0c;节点发现功能开启&#xff0c;但是目前我是单节点服务&#xff0c;所以尝试编辑sts将节点发现功能去掉或者在部署时将你的sts的yaml文件和chart文件修改重新部署以去掉该功能 {"t…...

Milvus×Florence:一文读懂如何构建多任务视觉模型

近两年来多任务学习&#xff08;Multi-task learning&#xff09;正取代传统的单任务学习&#xff08;single-task learning&#xff09;&#xff0c;逐渐成为人工智能领域的主流研究方向。其原因在于&#xff0c;多任务学习可以让我们以最少的人力投入&#xff0c;获得尽可能多…...

DAPP

02-DAPP 1 啥是 DApp&#xff1f; DApp&#xff0c;部署在链上的去中心化的应用。 DApp 是开放源代码&#xff0c;能运行在分布式网络上&#xff0c;通过网络中不同对等节点相互通信进行去中心化操作的应用。 DAPP 开放源代码&#xff0c;才能获得人的信任。如比特币&#xff…...

生产环境中,nginx 最多可以代理多少台服务器,这个应该考虑哪些参数 ?怎么计算呢

生产环境中&#xff0c;nginx 最多可以代理多少台服务器&#xff0c;这个应该考虑哪些参数 &#xff1f;怎么计算呢 关键参数计算方法评估步骤总结 在生产环境中&#xff0c;Nginx最多可以代理的服务器数量并没有一个固定的限制&#xff0c;它取决于多个因素&#xff0c;包括Ng…...

【深度学习|目标跟踪】StrongSORT 详解(以及StrongSORT++)

StrongSort详解 1、论文及源码2、DeepSORT回顾3、StrongSORT的EMA4、StrongSORT的NSA Kalman5、StrongSORT的MC6、StrongSORT的BOT特征提取器7、StrongSORT的AFLink8、StrongSORT的GSI模块 1、论文及源码 论文地址&#xff1a;https://arxiv.org/pdf/2202.13514 源码地址&#…...

23种设计模式-原型(Prototype)设计模式

文章目录 一.什么是原型设计模式&#xff1f;二.原型模式的特点三.原型模式的结构四.原型模式的优缺点五.原型模式的 C 实现六.原型模式的 Java 实现七. 代码解析八.总结 类图&#xff1a; 原型设计模式类图 一.什么是原型设计模式&#xff1f; 原型模式&#xff08;Prototype…...

Qt—QLineEdit 使用总结

文章参考:Qt—QLineEdit 使用总结 一、简述 QLineEdit是一个单行文本编辑控件。 使用者可以通过很多函数,输入和编辑单行文本,比如撤销、恢复、剪切、粘贴以及拖放等。 通过改变 QLineEdit 的 echoMode() ,可以设置其属性,比如以密码的形式输入。 文本的长度可以由 m…...

go-zero使用自定义模板实现统一格式的 body 响应

前提 go环境的配置、goctl的安装、go-zero的基本使用默认都会 需求 go-zero框架中&#xff0c;默认使用goctl命令生成的代码并没有统一响应格式&#xff0c;现在使用自定义模板实现统一响应格式&#xff1a; {"code": 0,"msg": "OK","d…...

BUGKU printf

整体思路 实现循环-->获取libc版本和system函数地址->将strcpy的got表项修改为system并获得shell 第一步&#xff1a;实现循环 从汇编语句可以看出&#xff0c;在每次循环结束时若0x201700处的值是否大于1则会继续循环。 encode1会将编码后的结果保存至0x2015c0处&am…...

深度学习:梯度下降法

损失函数 L&#xff1a;衡量单一训练样例的效果。 成本函数 J&#xff1a;用于衡量 w 和 b 的效果。 如何使用梯度下降法来训练或学习训练集上的参数w和b &#xff1f; 成本函数J是参数w和b的函数&#xff0c;它被定义为平均值&#xff1b; 损失函数L可以衡量你的算法效果&a…...

`console.log`调试完全指南

大家好&#xff0c;这里是 Geek技术前线。 今天我们来探讨 Console.log() 的一些优点。并分析一些基本概念和实践&#xff0c;这些可以让我们的调试工作变得更加高效。 理解前端 log 与后端 log 的区别 前端 log 与后端 log 有着显著的不同&#xff0c;理解这一点至关重要。…...

ROS VSCode调试方法

VSCode 调试 Ros文档 1.编译参数设置 cd catkin_ws catkin_make -DCMAKE_BUILD_TYPEDebug2.vscode 调试插件安装 可在扩展中安装(Ctrl Shift X): 1.ROS 2.C/C 3.C Intelliense 4.Msg Language Support 5.Txt Syntax 3.导入已有或者新建ROS工作空间 3.1 导入工作…...

16 —— Webpack多页面打包

需求&#xff1a;把 黑马头条登陆页面-内容页面 一起引入打包使用 步骤&#xff1a; 准备源码&#xff08;html、css、js&#xff09;放入相应位置&#xff0c;并改用模块化语法导出 原始content.html代码 <!DOCTYPE html> <html lang"en"><head&…...

微服务即时通讯系统的实现(服务端)----(3)

目录 1. 消息存储子服务的实现1.1 功能设计1.2 模块划分1.3 模块功能示意图1.4 数据管理1.4.1 数据库消息管理1.4.2 ES文本消息管理 1.5 接口的实现1.5.1 消息存储子服务所用到的protobuf接口实现1.5.2 最近N条消息获取接口实现1.5.3 指定时间段消息搜索接口实现1.5.4 关键字消…...

.net6.0 mvc 传递 model 实体参数(无法对 null 引用执行运行时绑定)

说一下情况&#xff1a; 代码没问题&#xff0c;能成功从数据库里查到数据&#xff0c;能将数据丢给ViewBag.XXXX, 在View页面也能获取到 ViewBag.XXXX的值&#xff0c;但是发布到线上后报这个错&#xff1a; Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 无法对 …...

VUE 入门级教程:开启 Vue.js 编程之旅

一、Vue.js 简介 Vue.js 是一套构建用户界面的渐进式 JavaScript 框架。它专注于视图层的开发&#xff0c;能够轻松地与其他库或现有项目进行整合。Vue.js 的核心库只关注视图层&#xff0c;通过简洁的 API 实现数据绑定和 DOM 操作的响应式更新&#xff0c;让开发者可以高效地…...

Ubantu系统docker运行成功拉取失败【成功解决】

解决docker运行成功拉取失败 失败报错 skysky-Legion-Y7000-IRX9:~$ docker run hello-world docker: permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Head “http://%2Fvar%2Frun%2Fdocker.sock/_ping”: dial uni…...

mvn-mac操作小记

1.安装brew 如果报错&#xff0c;Warning: /opt/homebrew/bin is not in your PATH. vim ~/.zshrc&#xff0c;最后一行追加 export PATH“/opt/homebrew/bin:$PATH” source ~/.zshrc 2.安装brew install maven mvn -version查看路径 Maven home: /opt/homebrew/Cellar/mav…...

机器学习——生成对抗网络(GANs):原理、进展与应用前景分析

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一. 生成对抗网络的基本原理二. 使用步骤2.1 对抗性训练2.2 损失函数 三. GAN的变种和进展四. 生成对抗网络的应用五. 持续挑战与未来发展方向六. 小结 前言 生…...

「Mac畅玩鸿蒙与硬件33」UI互动应用篇10 - 数字猜谜游戏

本篇将带你实现一个简单的数字猜谜游戏。用户输入一个数字&#xff0c;应用会判断是否接近目标数字&#xff0c;并提供提示“高一点”或“低一点”&#xff0c;直到用户猜中目标数字。这个小游戏结合状态管理和用户交互&#xff0c;是一个入门级的互动应用示例。 关键词 UI互…...

Ps:存储 Adobe PDF

在 Adobe Photoshop 中&#xff0c;将图像保存为 PDF 文件时&#xff0c; 会弹出“存储 Adobe PDF” Save Adobe PDF对话框。在此对话框中提供了多个选项&#xff0c;用于控制 PDF 文件的输出&#xff0c;包括一般设置&#xff08;选择预设、兼容性和保留编辑功能&#xff09;、…...

DDR3保姆级使用教程:ZYNQ 7010

内容:使用DDR3 IP核&#xff0c;向DDR3写入数据&#xff0c;然后再读出数据&#xff0c;通过串口打印。 设备&#xff1a;ZYNQ 7010 xc7z010clg-400-1。软件VIVADO 2018.3 &#xff08;1&#xff09;工程模块&#xff1a;一个写FIFO&#xff0c;一个读FIFO。一个ZYNQ IP核&am…...

中电科工程建设有限公司网站/营销推广主要包括

标签&#xff1a;screw 工具 php 代码 开源原创作品&#xff0c;允许转载&#xff0c;转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://colderboy.blog.51cto.com/485582/412303公司要求加密开发的代码源文件要求一部分加密一部分不…...

wordpress 安装插件 ftp/网购平台推广方案

前提条件 如果没有安装office的话&#xff0c;需要安装引擎 安装了office就不用安装引擎 连接数据库 Dim plMydb As Microsoft.Office.Interop.Access.Dao.DatabaseplMydb DAODBEngine_definst.OpenDatabase(ppDataBase) 备份数据库 AccessDB.CompactDatabase(iMDBFile, iTMPF…...

初二怎么做网站/网络广告的特点

【单选题】表达式2*3**2//8%7的计算结果是( ) 【单选题】以下选项中,不属于Python保留字的是( ) 【单选题】dict是一个字典变量,能够输出数字5的选项是( ) dict{"food":{"cake":1,"egg":5},"cake":2,"egg":3} 【判断题】用摆…...

外国做的中国动画视频网站/培训总结

多余的话不说&#xff0c;直接看代码&#xff1a; 1) 在 common/config/main.php 写入配置代码 1 // 配置翻译信息2 i18n > [3 translations > [4 app* > [ //app表示整个文件可以替换掉5 class &g…...

做品牌网站哪个好用/深圳百度seo培训

1、Windows平台 在windows命令行窗口下执行&#xff1a; C:/>netstat -ano 我们可以知道某一端口被那个进程&#xff08;对应PID&#xff09;占用&#xff1b; 然后我们可以打开任务管理器&#xff1b;查看某一PID对应的进程名&#xff1b; 如果PID没有显示&#xff0c;菜单…...

在线手机网站制作/沧州网站推广优化

一个bug解决&#xff1a; 有时在Vue工程中写es6语法代码会报regeneratorRuntime is not defined的错误&#xff0c;此时可通过下面方式解决&#xff1a; 下载npm install --save-dev babel-polyfill在webpack.config.js中写var babelpolyfill require("babel-polyfill&qu…...