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

【C语言】多线程

多线程

  • 线程
    • 线程的优点
  • C语言多线程
    • 创建线程
    • 终止线程
    • 连接和分离线程
  • 开启一个线程
    • 最基本的多线程实现
  • 开启两个线程
  • 多线程进行协同运算
      • 无参数传递的线程并发编程实例
      • 简单参数传递的线程并发编程实例
      • 结构体参数传递的线程并发编程实例
      • 线程的连接编程实例
      • 信号量同步进行写入
      • 互斥信号量实现对临界资源操作
    • 并发程序引起的共享内存的问题

在串口助手编程中,-k命令下需要实现等待接收message的同时可以发送键入message。但是,键入message使用的fgets()函数如果得不到键入就会一直等待,无法继续接收message,考虑采用多线程实现有键入则发送,否则一直等待接收message。

线程

线程在Unix系统下,通常被称为轻量级的进程,线程虽然不是进程,但却可以看作是Unix进程的表亲,同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。 一个进程可以有很多线程,每条线程并行执行不同的任务。

线程的优点

线程可以提高应用程序在多核环境下处理诸如文件I/O或者socket I/O等会产生堵塞的情况的表现性能。在Unix系统中,一个进程包含很多东西,包括可执行程序以及一大堆的诸如文件描述符地址空间等资源。在很多情况下,完成相关任务的不同代码间需要交换数据。如果采用多进程的方式,那么通信就需要在用户空间和内核空间进行频繁的切换,开销很大。但是如果使用多线程的方式,因为可以使用共享的全局变量,所以线程间的通信(数据交换)变得非常高效。

C语言多线程

多线程是多任务处理的一种特殊形式,多任务处理允许让电脑同时运行两个或两个以上的程序。一般情况下,两种类型的多任务处理:基于进程和基于线程。

基于进程的多任务处理是程序的并发执行。
基于线程的多任务处理是同一程序的片段的并发执行。
多线程程序包含可以同时运行的两个或多个部分。这样的程序中的每个部分称为一个线程,每个线程定义了一个单独的执行路径。

本教程假设您使用的是 Linux 操作系统,我们要使用 POSIX 编写多线程 C++ 程序。POSIX Threads 或 Pthreads 提供的 API 可在多种类 Unix POSIX 系统上可用,比如 FreeBSD、NetBSD、GNU/Linux、Mac OS X 和 Solaris。

创建线程

首先,c语言的多线程并发,需要用到 pthread.h 库。

#include <pthread.h>
For example: - pthread_t thrd1; - pthread_attr_t attr; - void thread_function(void argument); - char *some_argument;pthread_create(&thrd1, NULL, (void *)&thread_function, (void *) &some_argument)

线程创建函数包含四个变量,分别为: 1. 一个线程变量名,被创建线程的标识 2. 线程的属性指针,缺省为NULL即可 3. 被创建线程的程序代码 4. 程序代码的参数

创建一个POSIX 线程:

pthread_create (thread, attr, start_routine, arg)

pthread_create 创建一个新的线程,并让它可执行。

参数描述
thread指向线程标识符指针。
attr一个不透明的属性对象,可以被用来设置线程属性。您可以指定线程属性对象,也可以使用默认值 NULL。
start_routine线程运行函数起始地址,一旦线程被创建就会执行。
arg运行函数的参数。它必须通过把引用作为指针强制转换为 void 类型进行传递。如果没有传递参数,则使用 NULL。

创建线程成功时,函数返回 0,若返回值不为 0 则说明创建线程失败。

终止线程

pthread_exit(void *retval); //retval用于存放线程结束的退出状态

终止一个 POSIX 线程:

#include <pthread.h>
pthread_exit (status)

pthread_exit 用于显式地退出一个线程。通常情况下,pthread_exit() 函数是在线程完成工作后无需继续存在时被调用。

如果 main() 是在它所创建的线程之前结束,并通过 pthread_exit() 退出,那么其他线程将继续执行。否则,它们将在 main() 结束时自动被终止。

连接和分离线程

pthread_create调用成功以后,新线程和老线程谁先执行,谁后执行用户是不知道的,这一块取决与操作系统对线程的调度,如果我们需要等待指定线程结束,需要使用pthread_join函数,这个函数实际上类似与多进程编程中的waitpid。 举个例子,以下假设 A 线程调用 pthread_join 试图去操作B线程,该函数将A线程阻塞,直到B线程退出,当B线程退出以后,A线程会收集B线程的返回码。 该函数包含两个参数:

pthread_t th //th是要等待结束的线程的标识
void **thread_return //指针thread_return指向的位置存放的是终止线程的返回状态。
pthread_join (threadid, status) 
pthread_detach (threadid)

pthread_join() 子程序阻碍调用程序,直到指定的 threadid 线程终止为止。当创建一个线程时,它的某个属性会定义它是否是可连接的(joinable)或可分离的(detached)。只有创建时定义为可连接的线程才可以被连接。如果线程创建时被定义为可分离的,则它永远也不能被连。pthread_join() 函数来等待线程的完成。

开启一个线程

最基本的多线程实现

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>void* func(void *args){printf("hello\n");return NULL;
}int main(){pthread_t th;pthread_create(&th, NULL, func, NULL);pthread_join(th, NULL);return 0;
}

主要分为三步:

  1. 声明一个线程变量th,类型为pthread_t
  2. 使用pthread_create函数创建,第一个参数是线程变量的地址,第三个参数是线程执行的函数
  3. pthread_join函数等待;

注意:pthread库不是Linux系统默认的库,连接时需要使用库libpthread.a, 在使用pthread_create创建线程时,在编译中要加-lpthread参数:gcc xxx.c -lpthread -o xxx.o ./xxx

开启两个线程

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>void* func(void *args){int i;for(i=1; i<500; i++){printf("%d\n", i);}return NULL;
}int main(){pthread_t th1;pthread_t th2;pthread_create(&th1, NULL, func, NULL);pthread_create(&th2, NULL, func, NULL);pthread_join(th1, NULL);pthread_join(th2, NULL);return 0;
}

两个线程同时执行func函数,错序打印1~499
如果用到pthread_create函数的第4个参数,这个参数的传入会反应到func中的形参中去。

void* func(void *args){int i;char *name = (char*)args;for(i=1; i<500; i++){printf("%s:%d\n", name, i);}return NULL;
}int main(){pthread_t th1;pthread_t th2;pthread_create(&th1, NULL, func, "th1");pthread_create(&th2, NULL, func, "th2");pthread_join(th1, NULL);pthread_join(th2, NULL);return 0;
}

输出的结果,我们可以清晰地看出th1和th2的线程标记和交错运行。

多线程进行协同运算

创建一个数组,其中有5000个元素,我们想用两个线程来共同计算这5000个元素的加法和。

int arr[5000];
int s1 = 0;
int s2 = 0;
void *func1(void *args){int i;char *name = (char *)args;for(i = 1; i < 2500; i++){s1 += arr[i];}return NULL;
}
void *func1(void *args){int i;char *name = (char *)args;for(i = 2500; i < 5000; i++){s2 += arr[i];}return NULL;
}

从两个线程的函数可以看出,一个线程计算前2500个值的加法和,另一个线程计算后2500个值的加法和。

int main(){int i;for(i = 0; i <5000; i++){arr[i]  =  rand() % 50;}pthread_ th1;pthread_t th2;pthread_create(&th1, NULL, func1,  NULL);pthread_create(&th2, NULL, func2, NULL);pthread_join(th1, NULL);pthread_join(th2, NULL);printf("s1 = %d, s2 = %d, s1+s2 = %d\n", s1, s2, s1+s2);return 0;
}

main函数中,在pthread_join函数等待的th1th2都结束后,输出对应的值。

代码里,th1和th2的执行函数中有大量的相似代码,所以我们最后用一个函数来复用。不难想到,需要通过传参的方式来实现代码复用。这里我们定义一个结构体,结构体中有循环的起始标记first,终止标记last,区间内加法和result。

typedef struct{int first;int last;int result;
}MY_ARGS;
int arr[5000];
void *func(void *args){int i;int s = 0;//参数强制类型转换MY_ARGS *my_args = (MY_ARGS *)args;for(i = my_args->first; i < my_args->last; i++){s += arr[i];}my_args->result = s;return NULL;
}

func1fun2整合到了func中去。而在main函数中,我们创建线程的时候传入的参数正是结构体指针:

int main(){int i;for(i = 0; i < 5000; i++){arr[i] = rand() % 50;}pthread_t th1;pthread_t th2;MY_ARGS args1 = {0, 2500, 0};MY_ARGS args2 =  {2500, 5000, 0};pthread_create(&th1, NULL, func, &args1);pthread_create(&th2, NULL, func, &args2);pthread_join(th1, NULL);pthread_join(th2, NULL);printf("s1 = %d, s2 = %d, s1+s2 = %d\n", args1.result, args2.result, args1.result+args2.result);return 0;
}

这样在func函数中,我们就可以对传入的结构体参数中的元素进行利用了,将计算所得传到结构体的result中去。这样我们输出加法和,就可以得到跟上面一样的结果,但是代码会更整洁漂亮

无参数传递的线程并发编程实例

// 基于线程的并发编程
#include <stdio.h>
#include <pthread.h>
#define NUM_Threads 5// 线程的运行函数
void *PrintHello(void *arg)
{printf("Hello,World of Thread in C!\n");return 0;
}int main()
{int i;int ret;// 定义线程的id变量,多个变量使用数组pthread_t tids[NUM_Threads];for (i=0; i<NUM_Threads; i++){// 参数依次是: 创建的线程id,线程参数,调用的函数,传入的函数参数ret = pthread_create(&tids[i], NULL, PrintHello, NULL);if (ret != 0){printf("pthread_create error: error_code = \n");}}// 等各个线程推出后,进程才结束pthread_exit(NULL);return 0;
}/** 在CLion(Ubuntu)中输出结果为
Hello,World of Thread in C!
Hello,World of Thread in C!
Hello,World of Thread in C!
Hello,World of Thread in C!
Hello,World of Thread in C!* */

简单参数传递的线程并发编程实例

// 基于线程的并发编程,向线程传递参数1
// Test_2_createThread
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#define NUM_Threads 5// 线程的运行函数
void *PrintHelloId(void *threadid)
{// 对传入的参数进行强制类型转换,由无类型指针变为整形指针,然后再读取int tid = *((int *)threadid);printf("Hello,World, Thread %d\n",tid);return 0;
}int main()
{pthread_t pthreads[NUM_Threads];int i, rc;// 用数组存储i的数值int indexes[NUM_Threads];for (i=0; i<NUM_Threads; i++){printf("main() : 创建线程 %d \n",i);indexes[i] = i; // 保存i的数值// 在indexes传入参数的时候必须转换为无类型指针rc = pthread_create(&pthreads[i], NULL, PrintHelloId, (void *)&indexes[i]);if (0 != rc){printf("Error: 无法创建线程!\n");exit(-1);}}pthread_exit(NULL);return 0;
}/** 在CLion(Ubuntu)中输出结果是
main() : 创建线程 0
main() : 创建线程 1
Hello,World, Thread 0
main() : 创建线程 2
Hello,World, Thread 1
main() : 创建线程 3
Hello,World, Thread 2
main() : 创建线程 4
Hello,World, Thread 3
Hello,World, Thread 4* */

结构体参数传递的线程并发编程实例

// 基于线程的并发编程,向线程传递参数2(传递结构体)
// Test_3_createThread
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#define NUM_Threads 5typedef struct thread_data{int threadid;char message;
}THDATA,*PTHDATA;void * PrintHello(void * pthreadid)
{PTHDATA tid = (PTHDATA)pthreadid;printf("This is Pthread : %d ;info : %c \n",tid->threadid, tid->message);return 0;
}int main(void)
{pthread_t Pthread[NUM_Threads];THDATA index[NUM_Threads];int i, ret;for (i = 0; i < NUM_Threads; i++){printf("main() : 创建线程 %d \n",i);index[i].threadid = i;index[i].message = 'A'+i%10;ret = pthread_create(&Pthread[i], NULL, PrintHello, (void *)&index[i]);if (0 != ret){printf("Error: 创建线程失败!\n");exit(-1);}}pthread_exit(NULL);return 0;
}/** 在CLion(Ubuntu)中输出结果是
main() : 创建线程 0
main() : 创建线程 1
This is Pthread : 0 ;info : A
main() : 创建线程 2
main() : 创建线程 3
This is Pthread : 2 ;info : C
main() : 创建线程 4
This is Pthread : 3 ;info : D
This is Pthread : 4 ;info : E
This is Pthread : 1 ;info : B* */

线程的连接编程实例

// 基于线程的并发编程,连接或分离线程
// Test_4_createThread
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>#define NUM_Pthread 5void *PrintHello(void * pthreadid)
{int tid = *((int *)pthreadid);printf("Sleeping in thread %d ,...exiting \n",tid);return 0;
}int main(void)
{int i, ret;pthread_t Pthread[NUM_Pthread];pthread_attr_t attr; // 定义线程属性void * status;int index[NUM_Pthread];// 初始化并设置线程为可连接pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);for (i=0; i<NUM_Pthread; i++){printf("main() : 创建线程 %d \n",i);index[i] = i;ret = pthread_create(&Pthread[i], NULL, PrintHello, (void *)&index[i]);}// 删除属性,并等待其他线程pthread_attr_destroy(&attr);for (i=0; i<NUM_Pthread; i++){ret = pthread_join(Pthread[i], status);if (0 != ret){printf("Error: unable to join,%d\n",ret);exit(-1);}printf("main(): complete thread id : %d",i);printf(" exiting with status : %p\n",status);}printf("main() : program exiting.\n");pthread_exit(NULL);return 0;
}

信号量同步进行写入

// 用信号量进行同步
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <semaphore.h>#define Len 100       // 设置输入内容长度sem_t bin_sem;
char work_area[Len]; // 存放输入内容void *Thread_func(void *arg)
{// 等待信号量有大于0的值然后退出sem_wait(&bin_sem);while (0 != strncmp("end", work_area, 3)){printf("Input %ld characters\n", strlen(work_area)-1);}return 0;
}int main(void)
{int res;    // 存放命令的返回值pthread_t Pthread; // 创建线程void *thread_result; // 存放线程处理结果// 初始化信号量,并设置初始值为0res = sem_init(&bin_sem, 0, 0);if (0 != res){perror("Semaphore initialization failes");exit(EXIT_FAILURE);}// 创建新线程 0res = pthread_create(&Pthread, NULL, Thread_func, NULL);if (0 != res){perror("Thread creation failed");exit(EXIT_FAILURE);}printf("Enter 'end' to finish\n");// 当工作区内不是以end开头的字符串,则继续输入while (0 != strncmp("end", work_area, 3)){// 以标准输入获取输入到工作区内fgets(work_area, Len, stdin);sem_post(&bin_sem);  // 信号量+1}printf("\n Waiting for thread to finish...\n");// 等待线程结束res = pthread_join(Pthread, &thread_result);if (0 != res){perror("Thread join failed");exit(EXIT_FAILURE);}printf("Thread joined\n");sem_destroy(&bin_sem);  // 销毁信号量exit(EXIT_SUCCESS);return 0;
}

互斥信号量实现对临界资源操作

// 用互斥信号量进行同步
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>#define Len 3 // 自增计算次数
#define NUM_Pthread 5 // 设置线程的长度int count = 1; // 在数据段共享资源,多个进程抢占临界资源
// 对于临界资源,应该添加互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;void *Thread_func(void *threadid)
{int tid = *((int *)threadid);int i, val;printf("Pthread ID : %d \n",tid);for (i=0; i<NUM_Pthread; i++){pthread_mutex_lock(&mutex);val = count;printf("val = %d \n",val++);count = val;pthread_mutex_unlock(&mutex);}return 0;
}int main(void)
{int res;    // 存放命令的返回值int i;pthread_t Pthread[NUM_Pthread]; // 创建线程int index[NUM_Pthread];for (i=0; i<NUM_Pthread; i++){index[i] = i;// 创建线程res = pthread_create(&Pthread[i], NULL, Thread_func, (void *)&index[i]);if (0 != res){printf("Error: 创建线程失败!\n");exit(-1);}}for (i=0; i<NUM_Pthread; i++){// 汇合线程pthread_join(Pthread[i], NULL);}printf("count = %d\n",count);pthread_exit(NULL);return 0;
}// 在运行此程序无互斥锁时,我们不仅得到错误的答案,而且每次得到的答案都不相同
// 分析
// 当多个对等线程在一个处理器上并发运行时,机器指令以某种顺序完成,每个并发执行定义了线程中指令的某种顺序

并发程序引起的共享内存的问题

有两个进程,两个进程共享全局变量s。两个进程都执行一个计数功能的函数,直观地看过去,th1运行时s++要执行10000次,th2运行时s++也要执行10000次,似乎计算得到的最后s应该是20000。但实际上是这样的吗?

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
int s = 0;
void *func(void*args){int i;for(i = 0; i < 10000; i++){s++;}return NULL;
}
int main(){pthread_t th1;pthread_t th2;pthread_create(&th1, NULL, func, NULL);pthread_create(&th2, NULL, func, NULL);pthread_join(th1, NULL);pthread_join(th2, NULL);printf("%s = %d\n", s);return 0;
}

编译运行后,发现输出并不是20000,而是12657。

原因:当我们执行s++,底层发生的事件其实是:内存中读取s→将s+1→将s写入到内存。这不是一个原子化操作,当两个线程交错运行的时候,很容易发生结果的丢失。因此最后的结果肯定是要小于20000的。这种情况有种专有名词,叫race condition。

为了解决这个问题,我们可以加锁。

#include <pthread.h>
int s = 0;
pthread_mutex_t lock;	//锁的声明
void *func(void *args){int i;for(i = 0; i < 10000; i++){	//给临界区代码加锁实现原子化操作pthread_mutex_lock(&lock);s++;pthread_mutex_unlock(&lock);}return NULL;
}
int main(){pthread_t th1;pthread_t th2;//锁初始化pthread_mutex_init(&lock, NULL);pthread_create(&th1, NULL, func, NULL);pthread_createe(&th2, NULL, func, NULL);pthread_join(th1, NULL);pthread_join(th2, NULL);printf("s = %d\n, s);return 0;
}

改进后的代码如下,学过操作系统会很好理解,无非就是为了保证共享内存区(临界区)的原子化操作,我们可以在进这段代码之前加锁(pthread_mutex_lock),意味着其他线程看到这段内存被其他人占有的时候,就不去抢占,等这段内存被解锁(pthread_mutex_unlock)之后,它才有读写这段临界区的权利。

但其实这种方式的执行速度并不快,比如这段代码里,每个线程都要进行10000次加解锁的操作,它能解决内存读写冲突的问题,但是却牺牲了效率。

相关文章:

【C语言】多线程

多线程线程线程的优点C语言多线程创建线程终止线程连接和分离线程开启一个线程最基本的多线程实现开启两个线程多线程进行协同运算无参数传递的线程并发编程实例简单参数传递的线程并发编程实例结构体参数传递的线程并发编程实例线程的连接编程实例信号量同步进行写入互斥信号量…...

CDGA|浅谈“以治促用,以用促治”的数据治理战略

数据治理夯实企业数字化转型基础。采取“以治促用&#xff0c;以用促治”的数据治理战略&#xff0c;可以充分释放了企业核心运行要素的活力。 “以治促用”是指通过建立在数据治理链路及用户多维评估系统的基础上&#xff0c;对数据资产重新进行价值识别&#xff0c;推进高价值…...

Apifox-比postman更优秀的接口自动化测试平台

一、Apifox介绍 Apifox 是 API 文档、API 调试、API Mock、API 自动化测试一体化协作平台&#xff0c;定位 Postman Swagger Mock JMeter。通过一套系统、一份数据&#xff0c;解决多个系统之间的数据同步问题。只要定义好 API 文档&#xff0c;API 调试、API 数据 Mock、A…...

周期矩形波的傅里叶级数展开(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

前端预防XSS攻击全攻略

如何防止XSS攻击 一、是撒子 XSS攻击&#xff08;跨站点脚本攻击&#xff09;&#xff0c;就是黑客恶意篡改你网页的前端代码&#xff0c;在里面注入一些恶意的 htmljavascript的脚本&#xff0c;并在你的浏览器内运行&#xff0c;获取你的信息&#xff0c;或者进行一些恶意操…...

JUC(一)

1.AQS原理 1.1.概述 1>.AQS全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架; 2>.特点: ①.用state属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁; getState: 获取state状态;setStata: 设置…...

API接口——睡眠带开放能力

本文介绍睡眠带相关接口。 API 列表 请求方法API描述GET/v1.0/devices/{device_id}/sleep/daily-reports获取日睡眠报告。GET/v1.0/devices/{device_id}/sleep/monthly-reports获取月睡眠报告。GET/v1.0/devices/{device_id}/sleep/24h-reports获取 24 小时睡眠报告。GET/v1.…...

面向对象的一点小想法

接口里的方法可以写也可以不写 如果写的话&#xff0c;那么得是默认方法&#xff0c;需要在前面加个default 对于默认方法&#xff0c;能够重写&#xff0c;或者直接继承&#xff08;也就是直接用&#xff09; 比如下面&#xff1a; 就直接调用了接口的默认函数nibuhao&#…...

数据仓库工作问题总结

1. ODS 层采用什么压缩方式和存储格式&#xff1f; 压缩采用 Snappy &#xff0c;存储采用 orc &#xff0c;压缩比是 100g 数据压缩完 10g 左右。 2. DWD 层做了哪些事&#xff1f; 1.、数据清洗 空值去除过滤核心字段无意义的数据&#xff0c;比如订单表中订单 id 为 nul…...

Java常用算法

关于时间复杂度&#xff1a; 平方阶 (O(n2)) 排序 各类简单排序&#xff1a;直接插入、直接选择和冒泡排序。线性对数阶 (O(nlog2n)) 排序 快速排序、堆排序和归并排序。O(n1)) 排序&#xff0c; 是介于 0 和 1 之间的常数。希尔排序。线性阶 (O(n)) 排序 基数排序&#xff0c…...

插画网课平台排名

插画网课平台哪个好&#xff0c;插画网课排名靠前的有哪些&#xff0c;今天给大家梳理了国内5家专业的插画网课平台&#xff0c;各有优势和特色&#xff0c;给学插画的小伙伴提供选择&#xff0c;报插画网课一定要选择靠谱的&#xff0c;否则人钱两空泪两行&#xff01; 一&am…...

雷达、定位、跟踪等信号处理邻域SCI期刊整理及推荐

雷达邻域SCI期刊整理及推荐&#xff1a;题名、刊物信息、撰写特点、审稿周期及投稿难度总结 定位/跟踪邻域SCI期刊整理及推荐&#xff1a;题名、刊物信息、撰写特点、审稿周期及投稿难度总结 估计/滤波/融合等信号处理邻域SCI期刊整理及推荐&#xff1a;题名、刊物信息、撰写…...

NDK C++ 指针常量 常量指针 常量指针常量

指针常量 常量指针 常量指针常量// 指针常量 常量指针 常量指针常量#include <iostream> #include <string.h> #include <string.h>using namespace std;int main() {// *strcpy (char *__restrict, const char *__restrict);// strcpy()int number 9;int n…...

常见前端基础面试题(HTML,CSS,JS)(一)

html语义化的理解 代码结构: 使页面在没有css的情况下,也能够呈现出好的内容结构 有利于SEO: 爬虫根据标签来分配关键字的权重,因此可以和搜索引擎建立良好的沟通,帮助爬虫抓取更多的有效信息 方便其他设备解析&#xff1a; 如屏幕阅读器、盲人阅读器、移动设备等&#xff0c…...

Delphi RSA加解密

感谢、感谢、感谢大佬的分享&#xff0c;https://github.com/ZYHPRO/RSAEncryptAndDecode 目录 1. 前言 2. 准备工作 3. Demo注意事项说明 3.1 公钥、私钥文本格式 3.2 回车键的影响 3.3 中文加解密说明 4. 结语 1. 前言 最近工作上安排了一个项目&#xff0c;与工商银行之…...

oracle基本操作

文章目录基本操作用户权限管理&#xff1a;权限传递&#xff1a;角色管理&#xff1a;数据导出&#xff1a;对于远程数据库查看表空间查看表空间路径查看被锁的对象基本操作 connect sys/zxm as sysdba-- 用 sys用户登录 create user jsdx identified by jsdx 创建用户 jsdx 密…...

hive只复制表结构不复制表数据

目录 一、背景 二、准备测试数据 1.建表 2.造测试数据 三、操作 1.CTAS &#xff08;1&#xff09;.无分区表测试 &#xff08;2&#xff09;.分区表测试 2.LIKE &#xff08;1&#xff09;.无分区表测试 &#xff08;2&#xff09;.分区表测试 一、背景 有一张ori_…...

如何将Linux的NIC 名称更改为 eth0 而不是 enps33 或 enp0s25,只要几秒钟

概述 我们使用Linux系统&#xff0c;网卡名称通常都是eth0&#xff0c;但是有一些新的linux发行版&#xff0c;网卡名字 enps33 或 enp0s25。 pengubuntu:~$ ifconfig ens33 Link encap:Ethernet HWaddr 00:0c:29:fd:4d:3a inet addr:192.168.0.113 Bcast:192.168.0.…...

位运算笔记

1. 为什么要学位运算 因为这是计算机内部运算的语言&#xff0c;所以会非常快。 本人是因为学习算法经常遇见一些求二进制中的0和1的各种操作&#xff0c;好多都不知道所以特此整理一下&#xff0c;如有不对&#xff0c;烦请指正。 2. 什么是位运算 程序中的所有数在计算机内存…...

2023全国首个区块链平台发布,区块链绿色消费积分系统玩法悄然上市

全国首个区块链平台发布&#xff0c;区块链绿色消费积分系统玩法悄然上市 2023-02-23 16:15梦龙 大家好&#xff0c;我是你们熟悉而又陌生的好朋友梦龙&#xff0c;一个创业期的年轻人 2月22日&#xff0c;首届中国数字产权创新大会在成都举办。在本次大会上&#xff0c;全国…...

java_网络服务相关_gateway_nacos_feign区别联系

1. spring-cloud-starter-gateway 作用&#xff1a;作为微服务架构的网关&#xff0c;统一入口&#xff0c;处理所有外部请求。 核心能力&#xff1a; 路由转发&#xff08;基于路径、服务名等&#xff09;过滤器&#xff08;鉴权、限流、日志、Header 处理&#xff09;支持负…...

练习(含atoi的模拟实现,自定义类型等练习)

一、结构体大小的计算及位段 &#xff08;结构体大小计算及位段 详解请看&#xff1a;自定义类型&#xff1a;结构体进阶-CSDN博客&#xff09; 1.在32位系统环境&#xff0c;编译选项为4字节对齐&#xff0c;那么sizeof(A)和sizeof(B)是多少&#xff1f; #pragma pack(4)st…...

java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别

UnsatisfiedLinkError 在对接硬件设备中&#xff0c;我们会遇到使用 java 调用 dll文件 的情况&#xff0c;此时大概率出现UnsatisfiedLinkError链接错误&#xff0c;原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用&#xff0c;结果 dll 未实现 JNI 协…...

Qt Http Server模块功能及架构

Qt Http Server 是 Qt 6.0 中引入的一个新模块&#xff0c;它提供了一个轻量级的 HTTP 服务器实现&#xff0c;主要用于构建基于 HTTP 的应用程序和服务。 功能介绍&#xff1a; 主要功能 HTTP服务器功能&#xff1a; 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...

Python爬虫(二):爬虫完整流程

爬虫完整流程详解&#xff08;7大核心步骤实战技巧&#xff09; 一、爬虫完整工作流程 以下是爬虫开发的完整流程&#xff0c;我将结合具体技术点和实战经验展开说明&#xff1a; 1. 目标分析与前期准备 网站技术分析&#xff1a; 使用浏览器开发者工具&#xff08;F12&…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战

在现代战争中&#xff0c;电磁频谱已成为继陆、海、空、天之后的 “第五维战场”&#xff0c;雷达作为电磁频谱领域的关键装备&#xff0c;其干扰与抗干扰能力的较量&#xff0c;直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器&#xff0c;凭借数字射…...

Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?

Redis 的发布订阅&#xff08;Pub/Sub&#xff09;模式与专业的 MQ&#xff08;Message Queue&#xff09;如 Kafka、RabbitMQ 进行比较&#xff0c;核心的权衡点在于&#xff1a;简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...

C++:多态机制详解

目录 一. 多态的概念 1.静态多态&#xff08;编译时多态&#xff09; 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1&#xff09;.协变 2&#xff09;.析构函数的重写 5.override 和 final关键字 1&#…...

MySQL 部分重点知识篇

一、数据库对象 1. 主键 定义 &#xff1a;主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 &#xff1a;确保数据的完整性&#xff0c;便于数据的查询和管理。 示例 &#xff1a;在学生信息表中&#xff0c;学号可以作为主键&#xff…...

Vue3中的computer和watch

computed的写法 在页面中 <div>{{ calcNumber }}</div>script中 写法1 常用 import { computed, ref } from vue; let price ref(100);const priceAdd () > { //函数方法 price 1price.value ; }//计算属性 let calcNumber computed(() > {return ${p…...