【Linux】多线程:线程控制

news2024/9/20 18:35:56

目录

一、创建线程:pthread_create 

二、线程终止:pthread_exit、return、pthread_cancel

三、线程等待:pthread_join

四、线程分离:pthread_detach

五、如何创建并使用多线程 

六、对线程进行封装


一、创建线程:pthread_create 

pthread_create 是 POSIX 线程(pthread)库中的一个函数,用于创建一个新线程。它的函数原型如下:

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

各参数的详细说明如下:

  1. pthread_t *thread

    这是一个指向 pthread_t 类型的指针,用于保存新创建线程的线程标识符。pthread_t 是一个线程句柄,用于在之后的线程操作中引用该线程。
  2. const pthread_attr_t *attr

    这是一个指向 pthread_attr_t 类型的指针,指定线程的属性。如果设置为 NULL,则线程将使用默认属性。pthread_attr_t 用于定义线程的各种属性,例如栈大小、调度策略等。
  3. void *(*start_routine)(void*)

    这是一个指向函数的指针,函数的返回类型为 void*,接收一个 void* 类型的参数。这个函数将在新线程中执行。函数的签名应为:
    void* my_thread_function(void* arg);
    
  4. void *arg

    这是传递给 start_routine 函数的参数。由于是 void* 类型,调用者可以传递任何类型的数据,但需要在 start_routine 函数内进行类型转换。

返回值

  • pthread_create 成功时返回 0
  • 失败时返回一个错误代码,具体错误代码可以通过 man pthread_create 查找相关文档。常见的错误包括 EAGAIN(系统资源不足),EINVAL(无效的参数),EPERM(没有足够的权限)等。

示例代码

以下是一个简单的使用 pthread_create 的示例:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

// 线程执行的函数
void* my_thread_function(void* arg) {
    int* p = (int*)arg;
    printf("Thread received value: %d\n", *p);
    return NULL;
}

int main() {
    pthread_t thread;
    int value = 42;

    // 创建线程
    int ret = pthread_create(&thread, NULL, my_thread_function, &value);
    if (ret != 0) {
        fprintf(stderr, "Error creating thread: %d\n", ret);
        return EXIT_FAILURE;
    }

    // 等待线程结束
    pthread_join(thread, NULL);

    return EXIT_SUCCESS;
}

二、线程终止:pthread_exit、return、pthread_cancel

如果需要只终止某个线程而不终止整个进程,可以有三种方法:
1、 从线程函数 return。 但这种方法对主线程不适用,从 main 函数 return 相当于调用exit。

2、线程可以调用 pthread_ exit 终止自己。

3、 一个线程可以调用 pthread_ cancel 终止同一进程中的另一个线程。

【注意:线程不可使用exit函数,因为该函数是用来终止进程的。当进程中的某一个线程使用后,该进程也会终止!】

pthread_exit 函数用于终止当前线程的执行,并且可以选择性地返回一个值给调用 pthread_join 的线程。它的函数原型如下:

void pthread_exit(void *retval);

参数详解

  • void *retval
    • 这是一个指向 void 的指针,线程终止时返回的值。如果你希望主线程或其他线程通过 pthread_join 获取这个返回值,可以传递一个合适的指针。如果不需要返回值,可以传递 NULL

主要功能

  1. 终止线程

    调用 pthread_exit 会使当前线程立即终止。线程的资源(例如栈)在终止时会被释放,但线程的线程句柄和某些状态信息可能会被保留,直到其他线程调用 pthread_join 来回收这些资源。
  2. 返回值

    线程可以通过 pthread_exit 返回一个指针,这个指针将被传递给调用 pthread_join 的线程。这在需要线程间传递结果或状态时非常有用。
  3. 线程资源释放

    调用 pthread_exit 后,线程的资源会被保留,直到其他线程调用 pthread_join。这样可以避免由于线程终止而导致的资源泄漏问题。

示例代码

以下是一个简单的示例,展示了如何使用 pthread_exit 来终止线程并返回一个值:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

void* my_thread_function(void* arg) {
    int* result = malloc(sizeof(int));
    *result = 42; // 线程计算的结果

    // 线程结束,返回计算结果
    pthread_exit(result);
}

int main() {
    pthread_t thread;
    int* result;

    // 创建线程
    int ret = pthread_create(&thread, NULL, my_thread_function, NULL);
    if (ret != 0) {
        fprintf(stderr, "Error creating thread: %d\n", ret);
        return EXIT_FAILURE;
    }

    // 等待线程结束并获取返回值
    ret = pthread_join(thread, (void**)&result);
    if (ret != 0) {
        fprintf(stderr, "Error joining thread: %d\n", ret);
        return EXIT_FAILURE;
    }

    // 使用线程返回的结果
    printf("Thread returned value: %d\n", *result);
    free(result); // 释放动态分配的内存

    return EXIT_SUCCESS;
}

注意事项

  1. 资源管理

    在调用 pthread_exit 之前,确保线程的所有资源已经处理妥当。未处理的资源可能会导致内存泄漏或其他问题。
  2. 与 return 的区别

    在一个线程中使用 return 语句终止线程会隐式地调用 pthread_exit,并且线程返回值会作为 pthread_exit 的参数传递给 pthread_join。因此,return 和 pthread_exit 在功能上是等效的,但 pthread_exit 更明确地表示线程的结束和返回。

 【注意,pthread_exit 或者 return 返回的指针所指向的内存单元必须是全局的或者是用 malloc 分配的在堆上的空间,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。】

 【了解】pthread_cancel 函数用于请求取消一个正在运行的线程。它的函数原型如下:

int pthread_cancel(pthread_t thread);

参数详解

  • pthread_t thread
    • 这是你希望取消的线程的标识符,通常是通过 pthread_create 创建的线程句柄。

返回值

  • pthread_cancel 成功时返回 0
  • 失败时返回一个错误代码,常见的错误代码包括:
    • ESRCH:指定的线程无效或已终止。
    • EINVAL:线程不是当前进程的线程,或者线程已经是分离状态(即已取消)。

示例代码

以下是一个示例,演示了如何请求取消一个线程,并在线程中设置取消点:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

// 线程执行的函数
void* my_thread_function(void* arg) {
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); // 启用取消
    pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); // 取消请求在取消点处理

    printf("Thread is running...\n");

    // 模拟长时间运行的操作
    for (int i = 0; i < 10; ++i) {
        sleep(1); // 每秒钟检查一次取消请求
        printf("Working %d...\n", i);
    }

    printf("Thread exiting normally.\n");
    return NULL;
}

int main() {
    pthread_t thread;

    // 创建线程
    int ret = pthread_create(&thread, NULL, my_thread_function, NULL);
    if (ret != 0) {
        fprintf(stderr, "Error creating thread: %d\n", ret);
        return EXIT_FAILURE;
    }

    // 等待几秒钟,然后请求取消线程
    sleep(3);
    pthread_cancel(thread);

    // 等待线程结束
    pthread_join(thread, NULL);
    printf("Thread has been joined.\n");

    return EXIT_SUCCESS;
}

【注意:当线程被取消后,该线程的退出结果会被设置为-1。】

三、线程等待:pthread_join

pthread_join 函数,用于等待一个线程结束,是阻塞等待的,并获取该线程的退出状态。它的函数原型如下:

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

参数详解

  1. pthread_t thread

    这是你希望等待结束的线程的标识符,通常是通过 pthread_create 函数返回的线程句柄。
  2. void **retval

    这是一个指向 void* 类型的指针的指针,用于接收线程退出时返回的值。如果不需要获取线程的返回值,可以将其设置为 NULL。如果希望获取线程的返回值,传递一个指向 void* 的指针,pthread_join 会将线程的返回值存储在这个位置。

返回值

  • pthread_join 成功时返回 0
  • 失败时返回一个错误代码,常见的错误代码包括:
    • ESRCH:指定的线程无效或已终止。
    • EINVAL:线程不属于当前进程,或线程不可连接(例如线程已经是分离状态)。
    • EDEADLK:发生了死锁,通常是由于线程等待自己或者其他会导致死锁的条件。

使用示例

以下是一个简单的示例,展示了如何使用 pthread_join 等待线程结束并获取其返回值:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

// 线程执行的函数
void* my_thread_function(void* arg) {
    int* p = (int*)arg;
    printf("Thread received value: %d\n", *p);
    int* result = malloc(sizeof(int));
    *result = *p * 2; // 计算结果并返回
    pthread_exit(result); // 线程结束并返回结果
}

int main() {
    pthread_t thread;
    int value = 42;
    int* result;

    // 创建线程
    int ret = pthread_create(&thread, NULL, my_thread_function, &value);
    if (ret != 0) {
        fprintf(stderr, "Error creating thread: %d\n", ret);
        return EXIT_FAILURE;
    }

    // 等待线程结束并获取返回值
    ret = pthread_join(thread, (void**)&result);
    if (ret != 0) {
        fprintf(stderr, "Error joining thread: %d\n", ret);
        return EXIT_FAILURE;
    }

    // 使用线程返回的结果
    printf("Thread returned value: %d\n", *result);
    free(result); // 释放分配的内存

    return EXIT_SUCCESS;
}

注意事项

  1. 线程分离(Detached)

    如果线程处于分离状态(通过 pthread_detach 设置),则不能使用 pthread_join。否则,pthread_join 会返回 EINVAL 错误代码。
  2. 避免死锁

    不要在一个线程中等待另一个线程,而这个线程又在等待第一个线程。这样的操作会导致死锁。
  3. 清理资源

    如果线程返回了动态分配的内存(如上例所示),记得在主线程中使用 free 释放这些资源,避免内存泄漏。

为什么要进行线程等待?

        当线程退出时,在Linux内核中,该轻量级进程会销毁自身的资源。但在pthread库中,仍会保留线程控制块pthread_attr_t 结构体,该结构体中会留存线程的一系列属性和信息,其中就包括该线程执行方法结束时的返回值。而该线程控制块必须在被进行线程等待后才可以被回收所占用的资源,并且由pthread_join函数的输出型参数带出线程退出的返回值。

所以使用pthread_join函数进行线程等待是必要的,一是能够防止系统资源的浪费,对其及时进行清理,防止“僵尸线程”的产生;二是可以获得线程的退出状态或返回信息,获取线程的执行结果。  

四、线程分离:pthread_detach

pthread_detach 函数用于将线程设置为“分离状态”。在分离状态下,线程的资源会在其终止时自动释放,而无需其他线程调用 pthread_join 来显式回收这些资源。它的函数原型如下:

int pthread_detach(pthread_t thread);

参数详解

  • pthread_t thread
    • 这是你希望设置为分离状态的线程的标识符,通常是通过 pthread_create 创建的线程句柄。

返回值

  • 成功:返回 0
  • 失败:返回一个错误代码,常见的错误代码包括:
    • ESRCH:指定的线程无效或已终止。
    • EINVAL:指定的线程不是当前进程的线程,或者线程已经是分离状态。

分离状态的特点

  1. 自动资源释放

    一旦线程被设置为分离状态,它的资源(如线程句柄和线程栈)会在线程终止时自动释放。这样可以避免因未调用 pthread_join 而导致的资源泄漏。
  2. 不可再连接

    分离状态的线程不能再通过 pthread_join 被连接或回收。因此,如果你设置了线程为分离状态,必须确保线程结束时的资源不再需要显式管理。
  3. 使用场景

    当你创建的线程不需要由其他线程获取其退出状态或结果时,通常可以将其设置为分离状态。例如,后台线程或守护线程常常被设置为分离状态。

示例代码

以下是一个示例,展示了如何使用 pthread_detach 来将线程设置为分离状态:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void* my_thread_function(void* arg) {
    printf("Thread is running...\n");
    sleep(2); // 模拟线程工作
    printf("Thread is done.\n");
    return NULL;
}

int main() {
    pthread_t thread;

    // 创建线程
    int ret = pthread_create(&thread, NULL, my_thread_function, NULL);
    if (ret != 0) {
        fprintf(stderr, "Error creating thread: %d\n", ret);
        return EXIT_FAILURE;
    }

    // 将线程设置为分离状态
    ret = pthread_detach(thread);
    if (ret != 0) {
        fprintf(stderr, "Error detaching thread: %d\n", ret);
        return EXIT_FAILURE;
    }

    // 主线程继续执行其他操作
    printf("Main thread continues...\n");
    sleep(3); // 等待子线程完成

    printf("Main thread done.\n");
    return EXIT_SUCCESS;
}

注意事项

  1. 避免资源泄漏

    在将线程设置为分离状态之前,确保你不再需要获取线程的返回值或状态。如果需要获取线程的返回状态,应该使用 pthread_join,而不是 pthread_detach
  2. 线程状态

    如果一个线程已经结束并被设置为分离状态,尝试再次设置它为分离状态或尝试 pthread_join 将会失败。

当然,线程分离除了由主线程对其他线程进行分离外,还可以由线程本身进行分离,这时需要用到pthread_self函数获取该线程自身的线程ID。

pthread_self 函数用于获取调用线程的线程标识符(thread identifier)。这个函数在多线程编程中非常有用,因为它允许线程了解自己的身份,这在进行线程特定的操作或资源管理时可能是必要的。

函数原型

pthread_t pthread_self(void);

返回值

pthread_self 函数返回一个 pthread_t 类型的值,这个值代表调用该函数的线程的标识符。pthread_t 是一个线程库中定义的用于表示线程句柄的数据类型。

示例代码改写:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void* my_thread_function(void* arg) {
    // 将线程设置为分离状态
    ret = pthread_detach(pthread_self());
    if (ret != 0) {
        fprintf(stderr, "Error detaching thread: %d\n", ret);
        return (void*)-1;
    }
    printf("Thread is running...\n");
    sleep(2); // 模拟线程工作
    printf("Thread is done.\n");
    return NULL;
}

int main() {
    pthread_t thread;

    // 创建线程
    int ret = pthread_create(&thread, NULL, my_thread_function, NULL);
    if (ret != 0) {
        fprintf(stderr, "Error creating thread: %d\n", ret);
        return EXIT_FAILURE;
    }

    // 主线程继续执行其他操作
    printf("Main thread continues...\n");
    sleep(3); // 等待子线程完成

    printf("Main thread done.\n");
    return EXIT_SUCCESS;
}

五、如何创建并使用多线程 

在程序中,我们可以采取多种方式管理线程。在本节代码中,我们使用循环的方式创建多线程。那我们如何为每个线程分配任务呢?在学习完pthread_create函数之后,我们了解到该函数有一个形参为函数指针,也就是我们常说的“回调函数”,当线程创建完毕之后,该线程会立刻去执行该函数。

而在实际应用中,对于多线程的控制通常还会涉及到线程的互斥与同步,所以在本节代码当中我们仅展示如何简单地创建并使用多线程来执行各自的任务。

在多线程编程中,我们通常将主线程作为线程的控制总线,对创建的多线程进行统一管理。而主线程的任务就是:1、创建额外线程;2、保留每个线程的TID(线程ID);3、进行线程等待,获取线程的返回状态;4、主线程退出。【当然,在主线程中可以对其他线程进行取消操作;而在各自线程的内部也可以创建额外线程,但是这会使得多线程难于管理。】

在分支线程中,执行流程主要是:1、将需要执行的任务的函数指针通过参数传递给pthread_create函数,当线程创建完毕后,该线程就会跳转到该函数的起始地址处执行该函数的代码。2、进行线程退出,一般是通过return正常返回或者调用pthread_exit函数。

本次代码将会使用vector数组对每个线程的TID进行存储。

【需要注意的是,线程的创建数量并不是越多越好,而是要适量。在进行多线程编程时,要考虑线程的管理和CPU调度。一般情况下,线程数接近CPU核心数是理想的线程数量。在线程的互斥章节中我们会加深讲解。】

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <vector>

#define BUFF_SIZE 128

// 线程函数
void *thread_function(void *arg)
{
    char *name = (char *)arg;
    int cnt = 2;
    //执行2次
    while (cnt--)
    {
        printf("%s is running!!!\n", name);
        sleep(1);
    }
    // 在线程退出之前释放为每个线程所申请的资源
    delete[] name;
    // pthread_exit((void*)0); // 结束线程
    return (void *)0;
}

int main()
{
    // 存储每个线程的tid
    std::vector<pthread_t> tids;

    // 假设创建5个线程,此时加上主线程,该进程中共有6个线程
    for (int i = 0; i < 5; i++)
    {
        pthread_t tid = 0;
        // 注意,此处为什么不直接char buff[BUFF_SIZE] 呢?即为什么不直接在栈上创建空间呢?
        // 因为此处需要传递给pthread_create函数的参数是该处空间的地址。
        // 而在栈上创建的空间只存在于本次循环中,循环结束后该片空间会被销毁。在下次循环时,又会在相同的地址位置创建数组空间。
        // 进而多线程将会使用同一片内存区域,在之后创建的线程会影响之前的线程资源。这是不允许的。
        // 所以我们选择在堆上创建数组空间,使得每个线程的资源都是独立的!!!
        char *buff = new char[BUFF_SIZE];
        snprintf(buff, BUFF_SIZE, "i am thread-%d!", i + 1);
        int ret = pthread_create(&tid, NULL /*使用默认线程属性*/, thread_function, (void *)buff);
        if (ret != 0)
        {
            perror("pthread_create false!!!");
            exit(-1);
        }
        tids.emplace_back(tid);
    }

    // 对主线程所创建的额外线程进行阻塞等待,并获取线程的退出信息
    for (int i = 0; i < 5; i++)
    {
        //注意:指针变量本身也是有空间的,在64位系统下是8个字节。即指针变量本身也可以存储变量,如整型变量等
        void *res = NULL;
        int ret = pthread_join(tids[i], (void **)&res);
        if (ret != 0)
        {
            perror("pthread_join false!!!");
            exit(-1);
        }
        printf("thread-%d jioned success!!!, exit status is %ld\n", i + 1, (long)(res));
    }

    //主线程退出,同时也表示该进程退出
    return 0;
}

以上程序简单地演示了多线程的执行过程。实际上,多线程如果不加以控制,会造成资源竞争、程序难以控制、结果不符合预期等情况。而在后续的章节中,我们会着重讲解线程的互斥与同步,以解决多线程编程中不可预料的问题。 

六、对线程进行封装

#include <pthread.h>
#include <string>
#include <functional>
using FuncType = std::function<void(const std::string&)>;//包装器
//线程类的封装
class Thread
{
private:
    pthread_t _tid;//线程ID
    std::string _thread_name;//线程名
    FuncType _func;//线程的执行函数
    bool _is_running;//线程的状态
    //...
private:
    void Excute()
    {
        _is_running = true;
        _func(_thread_name);
        _is_running = false;
    }
    //类中的函数参数包含this指针,使用static修饰
    static void* ThreadRoute(void* arg)
    {
        Thread* self = static_cast<Thread*>(arg);
        self->Excute();
        return (void*)0;
    }
public:
    Thread(std::string thread_name, FuncType func)
    :_thread_name(thread_name), _func(func)
    {
        _is_running = false;
    }

    //线程启动
    bool Start(){
        int ret = pthread_create(&_tid, NULL, ThreadRoute, (void*)this);
        if (ret != 0){
            return false;
        }
        std::cout << _thread_name << " has Started" << std::endl;
        return true;
    }
    //线程取消
    bool Stop()
    {
        if(_is_running){
            int ret = pthread_cancel(_tid);
            if (ret != 0){
                return false;
            }   
            std::cout << _thread_name << " has Stoped" << std::endl;
            _is_running = false;
        }
        return true;
    }
    //回收线程
    bool Join()
    {
        if(!_is_running){
            int ret = pthread_join(_tid, NULL);//不关心线程返回值,设置为NULL
            if (ret != 0){
                return false;
            }   
        }
        std::cout << _thread_name << " has Joined" << std::endl;
        return true;
    }
};

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

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

相关文章

ModuleNotFoundError: No module named ‘keras.layers.core‘怎么解决

问题 ModuleNotFoundError: No module named keras.layers.core&#xff0c;如图所示&#xff1a; 如何解决 将from keras.layers.core import Dense,Activation改为from tensorflow.keras.layers import Dense,Activation&#xff0c;如图所示&#xff1a; 顺利运行&#xf…

中秋快到了,要给哪些国外客户送祝福(附贺卡模板)

马上就要中秋节了&#xff0c;在这里提前祝小伙伴们中秋节快乐&#xff0c;身体健康&#xff0c;阖家团圆&#xff0c;业绩越来越好&#xff0c;公司越来越好&#xff0c;一切都越来越好&#xff01; 中秋节是我们非常重要的几个传统节日之一了&#xff0c;除了我们自己庆祝之…

深入理解Java中的clone对象

目录 1. 为什么要使用clone 2. new和clone的区别 3. 复制对象和复制引用的区别 4.浅克隆和深克隆 5. 注意事项 1. 为什么要使用clone 在实际编程过程中&#xff0c;我们常常遇到这种情况&#xff1a;有一个对象 A&#xff0c;需要一个和 A 完全相同新对象 B&#xff0c;并…

【【通信协议之ARP的FPGA实现其一】】

通信协议之ARP的FPGA实现其一 介绍 ARP 协议分为 ARP 请求和 ARP 应答&#xff0c;源主机发起查询目的 MAC 地址的报文称为 ARP 请求&#xff0c;目的主机响应源主机并发送包含本地 MAC 地址的报文称为 ARP 应答。当主机需要找出这个网络中的另一个主机的物理地址时&#xff0…

点击化学 ,如何用最简单的试剂叠氮化修饰后用于Click Reaction?

“点击化学”这一术语由斯克里普斯研究所的K. B. Sharpless 于2001年首次提出&#xff0c;这是一类涉及碳-杂原子间 化学键形成的反应&#xff0c;该类反应具有收率高&#xff0c;选择性好的特 点。词条“点击”意为将分子片段拼接起来就像将安全带扣 环的两部分扣起来一样简单…

大学英语四六级报名照不通过的原因

大学英语四六级报名照不通过的原因 #英语四六级 #大学英语四六级 #大学英语四六级考试 #英语四六级报名照片 #英语四六级考试报名照片

数仓建模:数仓设计中的10个陷阱

目录 0 引言 1 主要内容 1.1 过于迷恋技术&#xff0c;而没有将重点放在业务需求和目标上 1.2 没有或无法找到一个有影响的、平易近人的、明白事理的高级管理人员作为数仓建设的发起人 1.3 将项目处理为一个巨大的持续多年的项目&#xff0c;而不是追求更容易管理的、虽然…

日光辐射系统室内太阳光模拟器

太阳光模拟器能够为实验室环境提供稳定可靠的光照环境&#xff0c;其作用相当于将自然太阳光“搬进”室内实验室。这对于研究太阳能电池、光伏材料及其他与太阳能相关的设备和材料性能至关重要。 1.氙灯灯泡功率&#xff1a;≥450W&#xff1b; 2.输出光束尺寸&#xff1a;≥22…

秃姐学AI系列之:实战Kaggle比赛:图像分类(CIFAR-10)

目录 准备工作 整理数据集 将验证集从原始的训练集中拆分出来 整理测试集 使用函数 图像增广 读取数据集 定义模型 定义训练函数 训练和验证数据集 对测试集进行分类并提交结果 准备工作 首先导入竞赛需要的包和模块 import collections import math import os i…

智能优化算法-樽海鞘优化算法(SSA)(附源码)

目录 1.内容介绍 2.部分代码 3.实验结果 4.内容获取 1.内容介绍 樽海鞘优化算法 (Salp Swarm Algorithm, SSA) 虽然名称中提到的是“樽海鞘”&#xff0c;但实际上这个算法是基于群体智能的一种元启发式优化算法&#xff0c;它模拟了樽海鞘&#xff08;Salps&#xff09;在海…

第67期 | GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以找…

Leetcode sql high frequency questions 50 (based)

high frequency SQL 50 (basic version) 高頻sql題目(Leetcode) 查詢 1757. 可回收且低脂的产品 Question 表&#xff1a;Products ---------------------- | Column Name | Type | ---------------------- | product_id | int | | low_fats | enum | | rec…

评测AI写毕业论文软件排行榜前十名的网站

在当今信息爆炸的时代&#xff0c;AI智能写作工具已经成为我们写作过程中的得力助手。特别是对于学术论文的撰写&#xff0c;这些工具不仅能够提高写作效率&#xff0c;还能帮助用户生成高质量的文稿。以下是五款值得推荐的AI智能写论文软件&#xff0c;其中特别推荐千笔-AIPas…

Mysql基础练习题 1729.求关注者的数量 (力扣)

编写解决方案&#xff0c;对于每一个用户&#xff0c;返回该用户的关注者数量。 #按 user_id 的顺序返回结果表 题目链接&#xff1a; https://leetcode.cn/problems/find-followers-count/description/ 建表插入语句&#xff1a; Create table If Not Exists Followers(us…

VMware Workstation Pro 17 下载教程(Window环境)

自从24年中旬&#xff0c;博通公司以 610 亿美元收购的 VMware 宣布对其虚拟化软件套件进行一些重大调整。Windows 和 Linux 版 VMware Workstation Pro 和 Mac 版 VMware Fusion 不再需要个人使用许可证&#xff0c;也就是对个人用户免费。 1. 下载方式 Windows 和 Linux 版…

IDEA 编译运行gradle项目

IDEA 编译运行gradle项目 本文介绍Gradle 的三种安装方式 1.IDEA 编译自动安装gradle【推荐】 2.mac brew 安装 gradle 3.手动 安装 gradle IDEA 编译gradle项目&#xff0c;之前的项目都是maven管理&#xff0c;今天遇到一个sping boot 的项目是用gradle管理的&#xff0c;下面…

[Linux]:文件(下)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;Linux学习 贝蒂的主页&#xff1a;Betty’s blog 1. 重定向原理 在明确了文件描述符的概念及其分配规则后&#xff0c;我们就可…

C程序设计(7.0安徽专升本函数)

在之前我很多代码执行都是放在函数里的&#xff0c;这样方便我管理和演示&#xff0c;现在能和大家更好的去了解函数了 考纲教材关于这个的理论知识太多了&#xff0c;且废话占大多数&#xff0c;甚至有些对于小白很晦涩难懂或容易搞混&#xff01;所以在这我就尽量缩减理论知…

C语言——希尔排序

希尔排序是对于插入排序的一种优化 代码&#xff1a; #include <stdio.h> #include <stdlib.h> void shell_sort(int* p, int len) { int i; int j; int step; int tmp; for (step len / 2; step > 0; step step / 2) { fo…

手把手教你实现微信小程序定位

实现小程序的定位 框架&#xff1a;uniappvue 1&#xff0c;用户授权配置 在pages.json文件中配置 "pages": [{"path": "pages/home/index","style": {"navigationBarTitleText": "首页","navigationBarBac…