【LinuxC】C语言线程(pthread)

news2024/11/19 5:56:57

文章目录

  • 一、 POSIX 线程库
    • 1.1 POSIX标准
    • 1.2 Pthreads
    • 1.2 数据类型、函数、宏
      • 1.21 数据类型
      • 1.22 函数
      • 1.23 宏
  • 二、创建线程
  • 三、线程同步
  • 四、线程销毁
  • 五、示例
    • 5.1 完整示例
    • 5.2 信号量示例

本专栏上一篇文章是Windows下(MSVC)的线程编程,需要的自行查看。
【C语言】Windows下的C语言线程编程详解

进程:进程是操作系统分配资源的基本单位,每个进程有独立的地址空间,进程间通信需要通过特定的机制。
线程:线程是进程中的一个执行单元,它们共享进程的资源。线程之间的切换比进程之间的切换代价小,因此线程编程可以提高程序的并发性能。
并发与并行:并发是指多个任务在同一时间段内交替执行,而并行是指多个任务在同一时刻同时执行。线程编程可以实现并发执行,但是否能够实现并行执行取决于系统的处理器数量和调度策略。

一、 POSIX 线程库

1.1 POSIX标准

POSIX标准是一系列确保操作系统之间兼容性的规范,旨在提升软件的可移植性

POSIX,全称为Portable Operating System Interface,即“可移植操作系统接口”,是由IEEE计算机协会制定的标准。它的主要目的是定义一套操作系统应该遵循的规则和接口,以保证在不同的操作系统之间能够运行相同的程序,而无需或只需很少的修改

POSIX标准涵盖了很多方面,包括系统调用、命令行 shell、文件系统接口、线程等。这些标准允许开发者编写的程序能够在支持POSIX的任何操作系统上运行,从而提高了软件的可移植性和互操作性。

总的来说,POSIX标准是Unix和类Unix系统(如Linux、MacOS)设计的核心部分,它通过提供一致的编程接口,帮助确保应用程序的可移植性和兼容性。

注意标准和实现的区别。POSIX标准主要由C语言实现,而C语言标准则主要关注语言本身的特性。

1.2 Pthreads

POSIX线程(英语:POSIX Threads,常被缩写为pthreads)是POSIX的线程标准,定义了创建和操纵线程的一套API。

实现POSIX线程标准的库常被称作pthreads,一般用于Unix-like POSIX系统,如Linux、 Solaris。但是Microsoft Windows上的实现也存在,例如直接使用Windows API实现的第三方库pthreads-w32;而利用Windows的SFU/SUA子系统,则可以使用微软提供的一部分原生POSIX API。

Pthreads定义了一套C语言的类型、函数与常量,它以pthread.h头文件和一个线程库实现。

Pthreads API中大致共有100个函数调用,全都以"pthread_"开头,并可以分为四类:

  • 线程管理,例如创建线程,等待(join)线程,查询线程状态等。
  • 互斥锁(Mutex):创建、摧毁、锁定、解锁、设置属性等操作
  • 条件变量(Condition Variable):创建、摧毁、等待、通知、设置与查询属性等操作
  • 使用了互斥锁的线程间的同步管理

POSIX的Semaphore API(包含在semaphore.h中)可以和Pthreads协同工作,但这并不是Pthreads的标准。因而这部分API是以"sem_“打头,而非"pthread_”。

1.2 数据类型、函数、宏

1.21 数据类型

  • pthread_t:线程句柄(Windows叫句柄,Linux叫标识符,知道即可)。它是一个结构体数据类型,用于唯一标识一个线程。出于移植目的,不能把它作为整数处理,应使用函数pthread_equal()对两个线程ID进行比较。获取自身所在线程id使用函数pthread_self()。

  • pthread_attr_t:线程属性。主要包括scope属性、detach属性、堆栈地址、堆栈大小、优先级。主要属性的意义如下:

    • __detachstate,表示新线程是否与进程中其他线程脱离同步。如果设置为PTHREAD_CREATE_DETACHED,则新线程不能用pthread_join()来同步,且在退出时自行释放所占用的资源。缺省为PTHREAD_CREATE_JOINABLE状态。可以在线程创建并运行以后用pthread_detach()来设置。一旦设置为PTHREAD_CREATE_DETACHED状态,不论是创建时设置还是运行时设置,则不能再恢复到PTHREAD_CREATE_JOINABLE状态。
    • __schedpolicy,表示新线程的调度策略,包括SCHED_OTHER(正常、非实时)、SCHED_RR(实时、轮转法)和SCHED_FIFO(实时、先入先出)三种,缺省为SCHED_OTHER,后两种调度策略仅对超级用户有效。运行时可以用过pthread_setschedparam()来改变。
    • __schedparam,一个struct sched_param结构,目前仅有一个sched_priority整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR或SCHED_FIFO)时才有效,并可以在运行时通过pthread_setschedparam()函数来改变,缺省为0。系统支持的最大和最小的优先级值可以用函数sched_get_priority_max和sched_get_priority_min得到。
    • __inheritsched,有两种值可供选择:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新线程使用显式指定调度策略和调度参数(即attr中的值),而后者表示继承调用者线程的值。缺省为PTHREAD_EXPLICIT_SCHED。
    • __scope,表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。POSIX的标准中定义了两个值:PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示与系统中所有线程一起竞争CPU时间,后者表示仅与同进程中的线程竞争CPU。目前LinuxThreads仅实现了PTHREAD_SCOPE_SYSTEM一值。
  • pthread_mutex_t:互斥锁的类型,用于保护共享资源免受多个线程的同时访问。

  • pthread_cond_t:条件变量的类型,用于线程间的同步,允许线程等待某个特定条件的发生。。

  • pthread_rwlock_t:读写锁的类型,允许多个线程同时读取共享资源,但只允许一个线程写入。

1.22 函数

线程操纵函数(简介起见,省略参数):

  • pthread_create():创建一个线程
  • pthread_exit():终止当前线程
  • pthread_cancel():请求中断另外一个线程的运行。被请求中断的线程会继续运行,直至到达某个取消点(Cancellation Point)。取消点是线程检查是否被取消并按照请求进行动作的一个位置。POSIX 的取消类型(Cancellation Type)有两种,一种是延迟取消(PTHREAD_CANCEL_DEFERRED),这是系统默认的取消类型,即在线程到达取消点之前,不会出现真正的取消;另外一种是异步取消(PHREAD_CANCEL_ASYNCHRONOUS),使用异步取消时,线程可以在任意时间取消。系统调用的取消点实际上是函数中取消类型被修改为异步取消至修改回延迟取消的时间段。几乎可以使线程挂起的库函数都会响应CANCEL信号,终止线程,包括sleep、delay等延时函数。
  • pthread_join():阻塞当前的线程,直到另外一个线程运行结束
  • pthread_kill():向指定ID的线程发送一个信号,如果线程不处理该信号,则按照信号默认的行为作用于整个进程。信号值0为保留信号,作用是根据函数的返回值判断线程是不是还活着。
  • pthread_cleanup_push():线程可以安排异常退出时需要调用的函数,这样的函数称为线程清理程序,线程可以创建多个清理程序。线程清理程序的入口地址使用栈保存,实行先进后处理原则。由pthread_cancel或pthread_exit引起的线程结束,会次序执行由pthread_cleanup_push压入的函数。线程函数执行return语句返回不会引起线程清理程序被执行。
  • pthread_cleanup_pop():以非0参数调用时,引起当前被弹出的线程清理程序执行。
  • pthread_setcancelstate():允许或禁止取消另外一个线程的运行。
  • pthread_setcanceltype():设置线程的取消类型为延迟取消或异步取消。

线程属性函数:

  • pthread_attr_init():初始化线程属性变量。运行后,pthread_attr_t结构所包含的内容是操作系统支持的线程的所有属性的默认值。
  • pthread_attr_setdetachstate():设置线程属性变量的detachstate属性(决定线程在终止时是否可以被joinable)
  • pthread_attr_getdetachstate():获取脱离状态的属性
  • pthread_attr_setscope():设置线程属性变量的__scope属性
  • pthread_attr_setschedparam():设置线程属性变量的schedparam属性,即调用的优先级。
  • pthread_attr_getschedparam():获取线程属性变量的schedparam属性,即调用的优先级。
  • pthread_attr_destroy():删除线程的属性,用无效值覆盖

互斥锁函数:

  • pthread_mutex_init() 初始化互斥锁
  • pthread_mutex_destroy() 删除互斥锁
  • pthread_mutex_lock():占有互斥锁(阻塞操作)
  • pthread_mutex_trylock():试图占有互斥锁(不阻塞操作)。即,当互斥锁空闲时,将占有该锁;否则,立即返回。
  • pthread_mutex_unlock(): 释放互斥锁
  • pthread_mutexattr_(): 互斥锁属性相关的函数

条件变量函数:

  • pthread_cond_init():初始化条件变量
  • pthread_cond_destroy():销毁条件变量
  • pthread_cond_signal(): 发送一个信号给正在当前条件变量的线程队列中处于阻塞等待状态的线程,使其脱离阻塞状态,唤醒后继续执行。如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。一般只给一个阻塞状态的线程发信号。假如有多个线程正在阻塞等待当前条件变量,则根据各等待线程优先级的高低确定哪个线程接收到信号开始继续执行。如果各线程优先级相同,则根据等待时间的长短来确定哪个线程获得信号。但pthread_cond_signal在多处理器上可能同时唤醒多个线程,当只能让一个被唤醒的线程处理某个任务时,其它被唤醒的线程就需要继续wait。POSIX规范要求pthread_cond_signal至少唤醒一个pthread_cond_wait上的线程,有些实现为了简便,在单处理器上也会唤醒多个线程。所以最好对pthread_cond_wait()使用while循环对条件变量是否满足做条件判断。
  • pthread_cond_wait(): 等待条件变量的特殊条件发生;pthread_cond_wait() 必须与一个pthread_mutex配套使用。该函数调用实际上依次做了3件事:对当前pthread_mutex解锁、把当前线程挂起到当前条件变量的线程队列、被其它线程的信号唤醒后对当前pthread_mutex申请加锁。如果线程收到一个信号被唤醒,将被配套的互斥锁重新锁住,pthread_cond_wait() 函数将不返回直到线程获得配套的互斥锁。需要注意的是,一个条件变量不应该与多个互斥锁配套使用。
  • pthread_cond_broadcast(): 某些应用,如线程池,pthread_cond_broadcast唤醒全部线程,但我们通常只需要一部分线程去做执行任务,所以其它的线程需要继续wait.
  • pthread_condattr_(): 条件变量属性相关的函数

线程私有存储(Thread-local storage):

  • pthread_key_create(): 分配用于标识进程中线程特定数据的pthread_key_t类型的键
  • pthread_key_delete(): 销毁现有线程特定数据键
  • pthread_setspecific(): 为指定线程的特定数据键设置绑定的值
  • pthread_getspecific(): 获取调用线程的键绑定值,并将该绑定存储在 value 指向的位置中

同步屏障函数

  • pthread_barrier_init(): 同步屏障初始化
  • pthread_barrier_wait():
  • pthread_barrier_destory():

其它多线程同步函数:

  • pthread_rwlock_*(): 读写锁

工具函数:

  • pthread_equal(): 对两个线程的线程标识号进行比较
  • pthread_detach(): 分离线程
  • pthread_self(): 查询线程自身线程标识号
  • pthread_once(): 某些需要仅执行一次的函数。其中第一个参数为pthread_once_t类型,是内部实现的互斥锁,保证在程序全局仅执行一次。

信号量函数,包含在semaphore.h中:

  • sem_init:用于初始化一个无名信号量。它需要三个参数:一个指向信号量对象的指针、一个表示信号量是否在多个进程间共享的标志,以及信号量的初始值。
  • sem_post:用于增加信号量的值,通常在释放资源时调用。它需要一个指向信号量对象的指针作为参数。
  • sem_wait:用于减少信号量的值,通常在请求资源时调用。如果信号量的值为0,调用此函数的线程将会被阻塞,直到信号量的值大于0。
  • sem_trywait:与sem_wait类似,但它是非阻塞的。如果信号量的值为0,它不会阻塞线程,而是立即返回。
  • sem_getvalue:用于获取信号量的当前值。它需要一个指向信号量对象的指针和一个用于存储信号量值的指针作为参数。
  • sem_destroy:用于销毁一个无名信号量。它需要一个指向信号量对象的指针作为参数。

共享内存函数,包含在sys/mman.h中,链接时使用rt库:

  • mmap:把一个文件或一个POSIX共享内存区对象映射到调用进程的地址空间。使用该函数的目的: 1.使用普通文件以提供内存映射I/O 2.使用特殊文件以提供匿名内存映射。 3.使用shm_open以提供无亲缘关系进程间的Posix共享内存区。
  • munmap: 删除一个映射关系
  • msync:文件与内存同步函数
  • shm_open:创建或打开共享内存区
  • shm_unlink:删除一个共享内存区对象的名字,删除一个名字仅仅防止后续的open,msq_open或sem_open调用获取成功。
  • ftruncate:调整文件或共享内存区大小
  • fstat来获取有关该对象的信息

1.23 宏

  • PTHREAD_CREATE_JOINABLE:创建的线程可以被其他线程回收资源。
  • PTHREAD_CREATE_DETACHED:创建的线程是分离的,不需要其他线程回收资源。
  • PTHREAD_CANCEL_ENABLE:允许取消。
  • PTHREAD_CANCEL_DISABLE:禁止取消。
  • PTHREAD_PROCESS_PRIVATE:线程特定数据只在创建它的进程内可见。
  • PTHREAD_PROCESS_SHARED:线程特定数据可以在不同进程间共享。
  • PTHREAD_MUTEX_INITIALIZER:静态初始化互斥锁。
  • PTHREAD_COND_INITIALIZER:静态初始化条件变量。

二、创建线程

(1)定义线程函数:

线程函数是线程执行的入口点,它的定义形式如下:

void *thread_function(void *arg)
{
    // 线程执行的代码
    return NULL;
}

这个函数的参数和返回值类型都必须是void*

(2)创建线程:

使用pthread_create函数创建线程,函数原型如下:

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

参数说明:

  • thread:指向线程标识符的指针,用于存储新创建的线程ID。
  • attr:指向线程属性的指针,用于设置线程的属性,如堆栈大小、调度策略等。如果设置为NULL,则使用默认属性。
  • start_routine:线程函数的起始地址。
  • arg:传递给线程函数的参数。

(3)等待线程结束:

使用pthread_join函数等待线程结束,函数原型如下:

int pthread_join(pthread_t thread, void **retval);

参数说明:

  • thread:要等待的线程ID。
  • retval:用于存储线程函数的返回值。

例:

pthread_t tid; // 线程ID
pthread_create(&tid, NULL, myThreadFunction, NULL);

三、线程同步

前一篇文章讲了线程同步的方式。这里以互斥锁为例。

互斥锁(Mutex):互斥锁是一种用于保护共享资源的机制,确保同一时刻只有一个线程可以访问共享资源。pthread库提供了pthread_mutex_t类型的互斥锁。

  • 初始化互斥锁:使用pthread_mutex_init函数初始化互斥锁。
  • 锁定互斥锁:使用pthread_mutex_lock函数锁定互斥锁。
  • 解锁互斥锁:使用pthread_mutex_unlock函数解锁互斥锁。
  • 销毁互斥锁:使用pthread_mutex_destroy函数销毁互斥锁。

(1)定义和初始化互斥锁:

pthread_mutex_t mutex; // 定义互斥锁变量
  • 静态初始化:使用 PTHREAD_MUTEX_INITIALIZER 宏,这种方式初始化的互斥锁是静态的,不能销毁。
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 静态初始化
  • 动态初始化:使用 pthread_mutex_init() 函数动态地初始化互斥锁,这种方式可以在需要的时候进行销毁。
pthread_mutex_t mutex; // 动态初始化
pthread_mutex_init(&mutex, NULL); // 动态初始化互斥锁

如果需要设置互斥锁的属性,可以创建一个 pthread_mutexattr_t 对象,设置属性,然后将其传递给 pthread_mutex_init() 函数的第二个参数。

(2) 在需要保护的临界区域加锁:

pthread_mutex_lock(&mutex);
// 访问共享资源的代码

(3)在使用完共享资源后解锁:

pthread_mutex_unlock(&mutex);

四、线程销毁

pthread_join(tid, NULL);

在 POSIX 线程(pthreads)编程中,销毁线程实际上是由操作系统自动完成的,当线程的运行终止时,它会被系统自动清理。

  1. 线程函数返回:线程函数执行完毕后,会返回一个值(通过 pthread_exit 或从线程函数返回),这会导致线程的终止。任何线程都能调用 pthread_exit 来结束自己的执行。

  2. 主线程等待子线程结束:使用 pthread_join 函数,主线程可以阻塞等待特定子线程结束。如果子线程已经结束,它的资源被释放,此时 pthread_join 返回。如果子线程还没有结束,主线程会被放入睡眠状态,直到子线程结束为止。

  3. 分离状态(Detach):如果不想使用 pthread_join 来等待子线程结束,你可以调用 pthread_detach 函数将线程置于分离状态。一旦线程处于分离状态,它将在终止时自动释放其资源,无需主线程显式等待。

  4. 取消线程:可以使用 pthread_cancel 函数来请求取消另一个线程的执行。被取消的线程可以选择清理并自行终止,或者立即终止,具体行为取决于该线程对取消状态的处理策略。

  5. 线程清理处理:当线程被取消或正常结束时,可以设置清理处理函数,这些函数会在线程退出前被调用,以执行必要的清理工作。可以使用 pthread_cleanup_push 注册这样的清理函数,并用 pthread_cleanup_pop 来配对它们。

  6. 线程资源的释放:线程结束时,它所占用的所有用户级资源应该已经被释放,例如动态分配的内存等。但是,线程使用的系统资源(如文件描述符、互斥锁等)通常由操作系统在线程结束后自动释放。

  7. 避免僵尸线程:在多线程程序中,确保所有线程都已经结束才让主线程退出是很重要的。否则,未被加入(joined)或分离(detached)的线程可能会变成所谓的“僵尸线程”,它们的进程状态不会释放,直到父进程结束。

  8. 错误检查:对于线程相关的函数调用(如 pthread_create, pthread_join, pthread_detach 等),应始终检查它们的返回值以确保没有发生错误。

五、示例

注意编译时指定链接库 -lpthread

5.1 完整示例

2个线程分别打印当前时间:线程创建后即运行,所以创建时间不同。

#include <stdio.h>
#include <unistd.h>
#include<pthread.h>
#include <sys/time.h>
#include<time.h>


void *print_time(void *arg){
    struct timeval current_time;
    if(gettimeofday(&current_time, NULL)==0){
       char *p = ctime(&current_time.tv_sec);
        printf("Current time is %s.\n",p);
    }
    else{
        return (void *)1;
    }
    return (void *)0;
}

int main(){
    pthread_t tid1,tid2;
    int ret1,ret2;

    ret1=pthread_create(&tid1, NULL,print_time,(void *)1);
    sleep(2);
    ret2=pthread_create(&tid2, NULL,print_time,(void *)2);

    if(ret1||ret2){
        printf("Creat threads ailed.\n");
        return 1;
    }

    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);

    return 0;
}

在这里插入图片描述

5.2 信号量示例

现在修改一下,线程只有获得信号量的时候才能打印时间。

  • sem_init(&semaphore, 0, 1);:初始化信号量为 1,表示初始时可以访问临界区。
  • sem_wait(&semaphore);:在访问临界区之前等待信号量,如果为 0 则阻塞,直到变为大于 0 才继续执行。
  • sem_post(&semaphore);:在临界区访问结束后增加信号量,唤醒其他等待的线程。

信号量:

  • 信号量的作用
    -信号量是一种计数器,用于控制多个线程对共享资源的访问。
    • 当一个线程想要访问共享资源时,首先要等待信号量。
    • 如果信号量的值大于 0,表示资源空闲,线程可以继续执行,并将信号量减 1。
    • 如果信号量的值为 0,表示资源已被占用,线程会被阻塞,直到信号量的值变为大于 0。
  • 初始化:int sem_init(sem_t *restrict sem, int pshared, unsigned int value);
    • sem: 指向要初始化的信号量的指针。
    • pshared: 如果是0,则信号量是进程内共享的;如果是非0值,则用于进程间共享(但很少使用,通常不推荐,因为这涉及到进程间共享内存的复杂性)。
    • value: 信号量的初始值。
  • P、V 操作:
    • P操作 (sem_wait 或 sem_trywait): 减少信号量的值。如果信号量的值在操作之前是正的,它就简单地减一。如果信号量的值在操作之前是零,线程将被阻塞,直到信号量的值变成正数,然后它才减一。
    • V操作 (sem_post): 增加信号量的值。这通常用来表示一个线程已经释放了它所持有的资源。
  • 信号量分类:
    • 二值信号量:通常用于实现互斥(Mutual Exclusion),确保在任何时刻只有一个线程或进程可以访问关键部分代码或资源。它只有两个值,通常是0(表示资源不可用)和1(表示资源可用)。当一个线程尝试获取值为0的信号量时,它将被阻塞直到信号量的值变为1。
    • 计数信号量:是更一般化的同步机制,允许一定数量的线程同时访问某个资源。它的值可以是任何非负整数,代表可用资源的数量。当一个线程想要访问资源时,它会执行P操作,使信号量减一;当线程释放资源时,它会执行V操作,使信号量加一。如果信号量的值小于或等于0,则到达的线程将被阻塞。
      在这里插入图片描述
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include <time.h>

sem_t semaphore;

void* printTime(void* arg) {
    sem_wait(&semaphore);
    time_t now;
    struct tm* timeinfo;
    time(&now);
    timeinfo = localtime(&now);
    printf("Thread ID: %ld, Current Time: %s", pthread_self(), asctime(timeinfo));
    sleep(2);  
    sem_post(&semaphore);
    return NULL;
}

int main() {
    pthread_t tid1, tid2;
    
    sem_init(&semaphore, 0, 1); // 初始化信号量为 1
    
    pthread_create(&tid1, NULL, printTime, NULL);
    pthread_create(&tid2, NULL, printTime, NULL);
    
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    
    sem_destroy(&semaphore);
    
    return 0;
}

在这里插入图片描述

把信号量初始值改为2,则2个线程可以同时打印时间:

在这里插入图片描述

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

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

相关文章

操作系统IO模型

IO模型 如何进行网络通信 Socket通信是进程通讯的一种方式&#xff0c;通过调用这个网络库的一些API函数可以实现分布在不同主机的相关进程之间的数据交换 网络编程的基本流程是什么&#xff1f; 服务端先创建socket套接字&#xff0c;然后用这个套接字去绑定并监听某个端口&a…

社交变革:探索Facebook的魔力

社交媒体平台的崛起已经改变了我们与世界的交互方式&#xff0c;而Facebook作为其中的巨头&#xff0c;其影响力和魔力更是不可忽视。本文将深入探讨Facebook如何引领社交变革&#xff0c;并探索其背后的魔力所在。 连接世界的纽带 Facebook的独特之处在于它作为一个社交平台&…

Android 地图SDK 绘制点 删除 指定

问题 Android 地图SDK 删除指定绘制点 详细问题 笔者进行Android 项目开发&#xff0c;对于已标记的绘制点&#xff0c;提供撤回按钮&#xff0c;即删除绘制点&#xff0c;如何实现。 解决方案 新增绘制点 private List<Marker> markerList new ArrayList<>…

泰迪智能科技携手华北电力大学理学院共建“校外实践基地”

3月15日&#xff0c;华北电力大学数理学院教学副主任史会峰、科研副主任王涛、概率教研室副主任解西阳莅临泰迪智能科技产教融合实训基地开展“华北电力大学校外实践教学基地”签约揭牌仪式。泰迪智能科技董事长张良均、支持中心负责人王宏刚、外联部吴桂锋进行接待。 活动伊始…

中霖教育好吗?口碑怎么样?

中霖教育专注于教育培训&#xff0c;口碑是非常好的&#xff0c;尤其是近年来职业证书考试受到了广大学生和家长的关注&#xff0c;一个机构的好坏和口碑是大家考虑的首要因素。 1、看教学质量 中霖教育的教师都是有着丰富教学经验的&#xff0c;不仅掌握扎实的基础知识&…

如何通过小程序上的产品力和品牌力提升用户的复购能力?

随着网络购物小程序的发展以及内容电商、社交电商、垂直电商、品牌自营等多个细分类型的出现&#xff0c;小程序成为用户日常购物、大促囤货以及首发抢购的重要场景&#xff0c;市场竞争也逐渐激烈。如何在用户侧获得更多转化、留存与复购&#xff0c;成为企业品牌日益关注的话…

svg代码应用于button

将svg代码的path属性应用于按钮内容&#xff0c;去掉按钮边框&#xff0c;并且自适应svg大小&#xff0c;以下实现的是一个旋转按钮。 svg代码如下(iconfont下载)&#xff1a; <svg t"1710741485848" class"icon" viewBox"0 0 1024 1024" ve…

RDP爆破

工具&#xff1a;超级弱口令检查工具 第一步&#xff1a;双击打开工具 第二步&#xff1a;导入账号 第三步&#xff1a;导入密码 第三步&#xff1a;线程 线程默认是50&#xff0c;如果担心影响业务可以修改为5 第四步&#xff1a;填写目标 第五步&#xff1a;选择需要检查的…

Docker知识--01

虚拟化 # 什么是虚拟化 在计算机中&#xff0c;虚拟化&#xff08;英语&#xff1a;Virtualization&#xff09;是一种资源管理技术&#xff0c;是将计算机的各种实体资源&#xff0c;如服务器、网络、内存及存储等&#xff0c;予以抽象、转换后呈现出来&#xff0c;打…

Django HTML模版

一个网站的基本框架&#xff08;如页面布局、导航栏、页脚栏等&#xff09;往往是相同的。可以把这个基本框架做成一个模版&#xff0c;其它正式的HTML页面可以直接套用这个模版&#xff0c;可以大减少各HTML文件的代码量。 语法&#xff08;模版文件中&#xff09;&#xff1…

论文阅读——BLIP

BLIP: Bootstrapping Language-Image Pre-training for Unified Vision-Language Understanding and Generation &#xff08;1&#xff09;单模态编码器&#xff0c;它分别对图像和文本进行编码。图像编码器用ViT&#xff0c;并使用附加的 [CLS] 标记来表示全局图像特征。文本…

【题目】【网络系统管理】2022年甘肃省职业院校技能大赛-网络构建-试卷

极安云科专注职业教育技能竞赛培训4年&#xff0c;包含信息安全管理与评估、网络系统管理、网络搭建等多个赛项及各大CTF模块培训学习服务。本团队基于赛项知识点&#xff0c;提供完整全面的系统性理论教学与技能培训&#xff0c;成立至今持续优化教学资源与讲师结构&#xff0…

s2fft库介绍:可微分和加速球谐变换

一、说明 科学和工程的许多领域都会遇到在球体上定义的数据。对此类数据进行建模和分析通常需要傅里叶变换的球面对应物&#xff0c;即球面谐波变换。我们简要概述了球谐变换&#xff0c;并提出了一种新的可微分算法&#xff0c;该算法专为GPU上的加速而定制[1]。该算法在最近发…

basic_string.h不同版本引起的异常崩溃

程序运行时报错&#xff0c;查看core文件&#xff0c;发现basic_string.h报错 经过排查发现 编译文件CMakeLists中设置了_GLIBCXX_USE_CXX11_ABI 0 _GLIBCXX_USE_CXX11_ABI 是C中的编译宏&#xff0c; 用来控制string及list使用的版本。 该宏仅在GCC5.1及后续版本中有效。 s…

说说JVM的垃圾回收机制

简介 垃圾回收机制英文为Garbage Collection, 所以我们常常称之为GC。那么为什么我们需要垃圾回收机制呢&#xff1f;如果大家有了解过Java虚拟机运行时区域的组成(JVM运行时存在&#xff0c;本地方法栈&#xff0c;虚拟机方法栈&#xff0c;程序计数器&#xff0c;堆&#xf…

第六节:使用SMB开发WebService

一、概述 webservice在日常开发中是常用的接口形式&#xff0c;SMB在设计之初就将webservice作为重要的代理协议。在组件库中提供了webservice input和webservice output两个组件&#xff0c;分别用于发布接口和调用接口。 二、发布webservice 在csdnProject工程中创建名为c…

C语言易错知识点:scanf函数

scanf在C语言学习中比较常用&#xff0c;但因为其涉及屏幕缓冲区导致有的时候会调入陷阱&#xff0c;下面分享一下常见的需要注意的事项&#xff1a; 1.输入末尾带有回车\n 当我们输入数据后&#xff0c;最后按下回车时&#xff0c;屏幕缓冲区的末尾都会含有这个字符 scanf的…

综合知识篇08-数据库系统考点(2024年软考高级系统架构设计师冲刺知识点总结系列文章)

专栏系列文章: 2024高级系统架构设计师备考资料(高频考点&真题&经验)https://blog.csdn.net/seeker1994/category_12593400.html案例分析篇00-【历年案例分析真题考点汇总】与【专栏文章案例分析高频考点目录】(2024年软考高级系统架构设计师冲刺知识点总结-案例…

【工具推荐】tmux 终端与会话分离神器 | 再也不用担心训练到一半不小心关闭终端导致训练终止了

文章目录 [toc] 0 什么是终端&#xff1f;什么是会话&#xff1f;1 tmux的安装2 tmux的基本操作2.1 启动与退出&#xff08;杀死tmux&#xff09;2.2 分离会话2.3 重接会话2.4 当前已有的会话查看2.5 切换会话 0 什么是终端&#xff1f;什么是会话&#xff1f; tmux是一个 ter…

每个私域运营者都必须掌握的 5 大关键流量运营核心打法!

很多人觉得私域运营比较简单&#xff0c;只是运营的事情&#xff0c;但事实并非如此&#xff0c;私域运营体系非常大&#xff0c;包含了公私域联动、品牌运营、品类战略&#xff0c;它是一个自上而下&#xff0c;由内到外的系统化工程。 很多人天天在想着如何引流拓客&#xf…