Linux系统之 — 线程

news2024/10/7 4:32:37

Linux系统之 — 线程

  • 线程介绍
  • 线程使用
    • 死锁(Deadlock)
    • 竞态条件(Race Condition)
  • 线程使用示例
    • 服务器端代码示例
      • 服务器端示例拆解
        • 1. 引入头文件和宏定义
        • 2. 定义全局变量
        • 3. 定义线程函数
        • 4. 主函数
        • 5. 错误处理和资源释放
    • 客户端代码示例
      • 客户端示例拆解
        • 1. 引入必要的头文件
        • 2. 定义服务器的IP地址、端口和缓冲区大小
        • 3. 主函数
        • 4. 创建套接字
        • 5. 错误检查
        • 6. 设置服务器地址结构
        • 7. 将点分十进制IP地址转换为二进制形式
        • 8. 连接到服务器
        • 9. 与服务器通信的循环
        • 10. 用户输入
        • 11. 退出条件
        • 12. 发送数据到服务器
        • 13. 接收服务器响应
        • 14. 关闭套接字
        • 15. 正常退出

线程介绍

在Linux系统中,线程是进程的一部分,是程序执行的最小单元。线程允许多个执行流程同时在同一个进程中运行,共享相同的内存空间和资源。

  1. 线程的定义

    • 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运行单位。
  2. 线程与进程的区别

    • 进程拥有独立的内存地址空间,而线程共享同一进程的内存地址空间。
    • 进程间通信(IPC)需要特定的机制,如管道、消息队列等,而线程间通信可以直接通过共享内存进行。
  3. 线程的调度

    • 线程由Linux内核的调度器进行调度,调度器根据线程的优先级和调度策略来决定线程的执行顺序。
    • 调度策略可以是静态优先级调度、动态优先级调度等。
  4. 线程的栈

    • 每个线程都有自己的栈空间,用于存储局部变量和调用栈信息。
  5. 线程的局限性

    • 由于线程共享同一进程的地址空间,一个线程的崩溃可能导致整个进程的崩溃。
    • 线程之间的同步和通信如果处理不当,可能会导致死锁、竞态条件等问题。
  6. 线程的实现

    • Linux内核支持两种线程实现方式:NPTL(Native POSIX Thread Library)和LinuxThreads。NPTL是较新的实现,提供了更好的性能和兼容性。

Linux线程是操作系统提供的一种并发执行机制,它允许在单个进程的上下文中运行多个执行流。线程共享进程的资源,如内存空间、文件描述符等,这使得线程之间的通信和数据共享变得非常高效,因为它们不需要通过进程间通信机制来交换信息。

线程的轻量级特性意味着它们比进程更易于创建和销毁,这减少了系统资源的消耗,并且可以更快地响应任务切换。这种快速的创建和销毁能力使得线程非常适合用于需要快速执行和频繁切换的应用程序,例如网络服务器和图形用户界面。

线程的并发执行能力提高了系统的效率,因为它允许多个任务同时进行,而不是顺序执行。这在处理大量并发请求或执行可以并行处理的计算任务时尤其有用。

线程还提供了同步机制,如互斥锁和条件变量,这些机制允许线程在需要时同步它们的执行,以避免数据竞争和其他并发问题。这对于需要保证数据一致性和顺序操作的应用程序至关重要。

在多核处理器上,线程可以被操作系统调度到不同的处理器核心上,实现真正的并行处理。这可以显著提高程序的性能,尤其是对于那些可以分解为多个独立任务的应用程序。

然而,线程的使用也带来了一些挑战,如线程安全问题、死锁和资源竞争。开发者需要仔细设计程序,以确保线程安全并避免这些问题。这通常涉及到使用锁、信号量和其他同步原语来控制对共享资源的访问。

Linux线程是实现高效并发编程的强大工具,它通过允许多个任务在单个进程中并发执行,提高了应用程序的性能和响应性。

线程使用

Linux线程的使用涉及到多方面的知识,包括线程的创建、同步、通信以及管理。

  1. 线程创建
    在Linux中,可以使用pthread库来创建线程。使用pthread_create()函数可以创建一个新的线程。

    #include <pthread.h>
    
    void *thread_function(void *arg) {
        // 线程要执行的代码
    }
    
    int main() {
        pthread_t thread_id;
        pthread_create(&thread_id, NULL, thread_function, NULL);
        pthread_join(thread_id, NULL); // 等待线程结束
        return 0;
    }
    
  2. 线程同步
    线程同步是确保多个线程在访问共享资源时不会发生冲突。可以使用互斥锁(mutexes)来实现同步。

    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    pthread_mutex_lock(&mutex); // 加锁
    // 访问共享资源
    pthread_mutex_unlock(&mutex); // 解锁
    
  3. 线程间通信
    线程间可以通过共享内存来通信。确保在访问共享内存时使用适当的同步机制来避免竞态条件。

  4. 线程属性设置
    使用pthread_attr_t可以设置线程的属性,如栈大小、调度策略等。

    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setstacksize(&attr, 1024); // 设置栈大小为1024字节
    pthread_create(&thread_id, &attr, thread_function, NULL);
    pthread_attr_destroy(&attr);
    
  5. 线程取消
    Linux线程可以被其他线程取消。使用pthread_cancel()可以请求取消一个线程。

    pthread_cancel(thread_id); // 请求取消线程
    
  6. 线程退出
    线程可以使用pthread_exit()来正常退出,并可以返回一个值给主线程。

    void *thread_function(void *arg) {
        // 线程执行的代码
        pthread_exit(NULL); // 线程退出
    }
    
  7. 线程清理
    使用pthread_cleanup_push()pthread_cleanup_pop()可以注册一个清理函数,该函数在线程退出时被调用。

    void thread_cleanup(void *arg) {
        // 清理资源
    }
    
    pthread_cleanup_push(thread_cleanup, NULL);
    // 线程代码
    pthread_cleanup_pop(0); // 0表示正常退出,非0表示取消
    
  8. 线程局部存储
    线程可以有自己的局部存储,使用pthread_key_create()创建一个键,用于访问线程特定的数据。

    pthread_key_t key;
    pthread_key_create(&key, NULL);
    pthread_setspecific(key, (void*)data); // 设置线程特定的数据
    void *data = pthread_getspecific(key); // 获取线程特定的数据
    
  9. 线程调度
    线程的调度可以通过设置不同的调度策略来控制,例如SCHED_FIFOSCHED_RR

  10. 线程安全
    编写线程安全代码是使用线程时的一个重要考虑。确保所有共享资源都通过适当的同步机制进行保护。

使用线程时,需要考虑线程的生命周期管理、资源的同步访问以及线程之间的协作。正确地使用线程可以提高程序的性能和响应性,但同时也需要仔细设计以避免并发问题,如死锁、竞态条件等。

死锁和竞态条件是并发编程中常见的问题,它们都与多个线程或进程对共享资源的访问有关。

死锁(Deadlock)

死锁是指两个或多个线程在执行过程中因争夺资源而造成的一种僵局。当每个线程都持有一个资源并等待其他线程释放它们需要的资源时,如果没有适当的机制来解决这种循环等待,那么这些线程就会永远等待下去,无法继续执行。

死锁的四个必要条件

  1. 互斥:资源在一段时间内只能被一个线程使用。
  2. 占有和等待:线程至少持有一个资源,并且正在等待获取其他线程持有的资源。
  3. 不可剥夺:资源一旦被线程占有,就不能被其他线程强行剥夺,只能由占有它的线程主动释放。
  4. 循环等待:存在一个线程资源的循环等待链,每个线程都在等待下一个线程所占有的资源。

解决死锁的方法

  • 预防:通过设计来破坏死锁的必要条件之一。
  • 避免:使用算法动态检测资源分配的安全性。
  • 检测:允许死锁发生,然后检测并恢复。
  • 解除:通过终止或回滚线程来释放资源。

竞态条件(Race Condition)

竞态条件发生在多个线程访问共享数据时,其执行顺序影响结果,而程序员并没有对这种顺序进行适当的同步。当多个线程试图同时修改同一变量或数据结构,并且最终结果依赖于这些修改的顺序时,就会发生竞态条件。

竞态条件的例子
假设有两个线程同时增加一个全局计数器的值:

int counter = 0;

void increment() {
    counter++; // 读取counter的值,增加1,然后写回
}

int main() {
    pthread_t t1, t2;
    pthread_create(&t1, NULL, (void*)increment, NULL);
    pthread_create(&t2, NULL, (void*)increment, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    return counter; // 期望结果是2,但由于竞态条件,实际结果可能是1
}

解决竞态条件的方法

  • 互斥:使用互斥锁或其他同步机制,确保一次只有一个线程可以访问共享资源。
  • 原子操作:使用原子操作来保证操作的不可分割性。
  • :使用读写锁等锁机制来控制对共享资源的访问。

线程使用示例

这是一个使用pthread库在Linux上创建线程来处理TCP连接的简单示例。这个程序将创建一个服务器,它能够接受客户端的连接请求,并使用线程来处理每个连接。
目的:了解如何使用线程来并发处理多个TCP连接。每个线程独立地处理一个客户端连接,而主线程继续监听新的连接请求。这种模型适用于需要同时服务多个客户端的服务器应用程序。

服务器端代码示例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>

#define PORT 8080
#define MAX_CLIENTS 5

// 互斥锁,用于同步对客户端数组的访问
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

// 客户端数组,存储活跃的客户端连接
int client_sockets[MAX_CLIENTS];

// 线程函数,用于处理客户端请求
void* handle_client(void* arg) {
    int client_socket = *((int*) arg);
    free(arg); // 释放分配给client_socket的内存

    char buffer[1024];
    while (1) {
        int bytes_read = read(client_socket, buffer, sizeof(buffer));
        if (bytes_read <= 0) {
            // 客户端断开连接
            close(client_socket);
            pthread_mutex_lock(&mutex);
            int i;
            for (i = 0; i < MAX_CLIENTS; i++) {
                if (client_sockets[i] == client_socket) {
                    client_sockets[i] = -1; // 标记为无效连接
                    break;
                }
            }
            pthread_mutex_unlock(&mutex);
            break;
        }

        // 简单的回显服务器,将接收到的数据发送回客户端
        write(client_socket, buffer, bytes_read);
    }

    return NULL;
}

int main() {
    int server_socket, client_socket;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);

    // 创建TCP服务器套接字
    server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    // 绑定服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);
    if (bind(server_socket, (struct sockaddr*) &server_addr, sizeof(server_addr)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 监听连接请求
    listen(server_socket, MAX_CLIENTS);

    printf("Server listening on port %d...\n", PORT);

    while (1) {
        client_socket = accept(server_socket, (struct sockaddr*) &client_addr, &client_len);
        if (client_socket < 0) {
            perror("accept failed");
            continue;
        }

        // 锁定互斥锁以安全地添加新的客户端连接
        pthread_mutex_lock(&mutex);
        for (int i = 0; i < MAX_CLIENTS; i++) {
            if (client_sockets[i] == -1) {
                client_sockets[i] = client_socket;
                break;
            }
        }
        pthread_mutex_unlock(&mutex);

        // 创建线程来处理客户端连接
        pthread_t thread_id;
        int* temp = malloc(sizeof(int)); // 临时存储client_socket
        *temp = client_socket;
        if (pthread_create(&thread_id, NULL, handle_client, temp) != 0) {
            perror("pthread_create failed");
            close(client_socket);
        }
    }

    close(server_socket);
    return 0;
}

说明:

  1. 初始化互斥锁pthread_mutex_t mutex用于同步对client_sockets数组的访问。

  2. 创建服务器套接字:使用socket()函数创建一个TCP套接字。

  3. 绑定地址:使用bind()函数将服务器地址绑定到套接字上。

  4. 监听连接:使用listen()函数使服务器套接字进入监听状态。

  5. 接受连接:使用accept()函数接受客户端的连接请求。

  6. 处理客户端连接:为每个客户端连接创建一个新线程,使用pthread_create()函数。

  7. 线程函数handle_client()是线程执行的函数,它读取客户端发送的数据,并将其回显给客户端。

  8. 互斥锁的使用:在添加新客户端到client_sockets数组时使用互斥锁来避免竞态条件。

  9. 关闭连接:当客户端断开连接或发生错误时,关闭套接字并从数组中移除该客户端。

服务器端示例拆解

1. 引入头文件和宏定义
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>

#define PORT 8080
#define MAX_CLIENTS 5

这里引入了必要的头文件,用于处理套接字、线程和基本的输入输出。PORT定义了服务器监听的端口号,MAX_CLIENTS定义了服务器能够同时处理的最大客户端数量。

2. 定义全局变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int client_sockets[MAX_CLIENTS];

这里定义了一个互斥锁mutex,用于同步对client_sockets数组的访问。client_sockets是一个整型数组,用于存储当前活跃的客户端套接字。

3. 定义线程函数
void* handle_client(void* arg) {
    int client_socket = *((int*) arg);
    free(arg); // 释放分配给client_socket的内存

    char buffer[1024];
    while (1) {
        int bytes_read = read(client_socket, buffer, sizeof(buffer));
        if (bytes_read <= 0) {
            // 客户端断开连接
            close(client_socket);
            pthread_mutex_lock(&mutex);
            int i;
            for (i = 0; i < MAX_CLIENTS; i++) {
                if (client_sockets[i] == client_socket) {
                    client_sockets[i] = -1; // 标记为无效连接
                    break;
                }
            }
            pthread_mutex_unlock(&mutex);
            break;
        }

        // 简单的回显服务器,将接收到的数据发送回客户端
        write(client_socket, buffer, bytes_read);
    }

    return NULL;
}

这是线程执行的函数handle_client。它接收一个指向int的指针作为参数,这个指针指向客户端的套接字描述符。函数首先将指针解引用并释放分配的内存。然后,它进入一个无限循环,不断读取客户端发送的数据,并将其回显给客户端。如果读取的字节数小于或等于0,表示客户端已经断开连接,此时关闭套接字,更新client_sockets数组,并退出循环。

4. 主函数
int main() {
    int server_socket, client_socket;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);

    // 创建TCP服务器套接字
    server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    // 绑定服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);
    if (bind(server_socket, (struct sockaddr*) &server_addr, sizeof(server_addr)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 监听连接请求
    listen(server_socket, MAX_CLIENTS);

    printf("Server listening on port %d...\n", PORT);

    while (1) {
        client_socket = accept(server_socket, (struct sockaddr*) &client_addr, &client_len);
        if (client_socket < 0) {
            perror("accept failed");
            continue;
        }

        // 锁定互斥锁以安全地添加新的客户端连接
        pthread_mutex_lock(&mutex);
        for (int i = 0; i < MAX_CLIENTS; i++) {
            if (client_sockets[i] == -1) {
                client_sockets[i] = client_socket;
                break;
            }
        }
        pthread_mutex_unlock(&mutex);

        // 创建线程来处理客户端连接
        pthread_t thread_id;
        int* temp = malloc(sizeof(int)); // 临时存储client_socket
        *temp = client_socket;
        if (pthread_create(&thread_id, NULL, handle_client, temp) != 0) {
            perror("pthread_create failed");
            close(client_socket);
        }
    }

    close(server_socket);
    return 0;
}

main函数首先创建一个TCP套接字,并将其绑定到服务器的地址和端口上。然后,它监听端口上的连接请求。当接受到一个新的客户端连接时,accept函数返回一个新的套接字描述符,该描述符用于与客户端通信。

接下来,代码使用互斥锁来确保对client_sockets数组的访问是安全的。它检查数组以找到第一个空位,并将新的客户端套接字存储在那里。

然后,代码尝试创建一个新的线程来处理这个客户端。为此,它首先为客户端套接字分配内存,并将其作为参数传递给pthread_create。如果线程创建失败,它将关闭客户端套接字并打印错误消息。

最后,主循环继续监听新的连接请求,而新创建的线程将独立地处理客户端通信。

5. 错误处理和资源释放

示例中包含了错误处理,例如在创建套接字、绑定地址、监听连接、接受连接和创建线程时检查错误。如果发生错误,将打印错误消息并适当地退出或继续执行。此外,当客户端断开连接时,示例会关闭套接字并释放相关资源。

客户端代码示例

客户端将展示如何使用套接字与服务器建立TCP连接,发送数据,并接收服务器的响应。

目的: 客户端程序是一个简单的回显客户端,它连接到服务器,发送消息,并接收服务器的回显响应。用户可以输入消息并发送,直到输入"exit"命令来退出程序。

该示例是基本的套接字通信过程,包括创建套接字、设置地址、建立连接、发送和接收数据以及关闭连接。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>

#define SERVER_IP "127.0.0.1" // 服务器的IP地址
#define PORT 8080             // 服务器监听的端口号
#define BUFFER_SIZE 1024      // 数据缓冲区的大小

int main() {
    int sock;
    struct sockaddr_in server_addr;
    char buffer[BUFFER_SIZE];

    // 创建TCP客户端套接字
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1) {
        perror("Could not create socket");
        return 1;
    }

    // 设置服务器地址结构
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
        perror("Invalid address or Address family not supported ");
        return 1;
    }

    // 连接到服务器
    if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("Connection Failed");
        close(sock);
        return 1;
    }

    printf("Connected to the server.\n");

    // 发送数据到服务器
    while (1) {
        printf("Enter message to send to server (or 'exit' to quit): ");
        fgets(buffer, BUFFER_SIZE, stdin);
        if (strcmp(buffer, "exit\n") == 0) {
            break;
        }

        // 发送数据
        if (send(sock, buffer, strlen(buffer), 0) < 0) {
            perror("Send failed");
            break;
        }

        // 接收服务器响应
        if (recv(sock, buffer, BUFFER_SIZE, 0) < 0) {
            perror("Receive failed");
            break;
        }

        printf("Received from server: %s", buffer);
    }

    // 关闭连接
    close(sock);
    printf("Disconnected from the server.\n");

    return 0;
}

说明:

  1. 引入头文件:包括了处理套接字和基本输入输出的头文件。

  2. 定义常量:包括服务器的IP地址、端口号和缓冲区大小。

  3. 创建套接字:使用socket()函数创建一个新的TCP套接字。

  4. 设置服务器地址:使用sockaddr_in结构体设置服务器的IP地址和端口号。

  5. 连接服务器:使用connect()函数尝试连接到服务器。

  6. 输入循环:客户端进入一个循环,提示用户输入消息。如果用户输入"exit",则退出循环。

  7. 发送数据:使用send()函数将用户输入的消息发送到服务器。

  8. 接收响应:使用recv()函数接收服务器回显的消息。

  9. 错误处理:在创建套接字、解析地址、连接服务器、发送数据和接收数据的过程中,如果遇到错误,将打印错误消息并退出程序。

  10. 关闭套接字:在用户退出程序或发生错误时,关闭套接字以释放资源。

客户端示例拆解

1. 引入必要的头文件
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>

头文件提供了创建套接字、进行网络通信以及处理字符串等所需的函数和数据结构。

2. 定义服务器的IP地址、端口和缓冲区大小
#define SERVER_IP "127.0.0.1" // 服务器的IP地址
#define PORT 8080             // 服务器监听的端口号
#define BUFFER_SIZE 1024      // 数据缓冲区的大小

宏定义提供了客户端连接服务器所需的参数。

3. 主函数
int main() {
    // ...
}

main函数是程序的入口点。

4. 创建套接字
int sock;
sock = socket(AF_INET, SOCK_STREAM, 0);

使用socket()函数创建一个新的套接字。AF_INET指定使用IPv4地址族,SOCK_STREAM指定使用TCP协议。

5. 错误检查
if (sock == -1) {
    perror("Could not create socket");
    return 1;
}

如果套接字创建失败,将打印错误消息并返回非零值,表示程序异常退出。

6. 设置服务器地址结构
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);

创建一个sockaddr_in结构体并初始化,设置服务器的地址族和端口号。htons()函数用于将主机字节序的端口号转换为网络字节序。

7. 将点分十进制IP地址转换为二进制形式
if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
    perror("Invalid address or Address family not supported ");
    return 1;
}

使用inet_pton()函数将字符串形式的IP地址转换为网络字节序的二进制形式,并存储在server_addr结构体中。

8. 连接到服务器
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
    perror("Connection Failed");
    close(sock);
    return 1;
}

使用connect()函数尝试连接到服务器。如果连接失败,打印错误消息,关闭套接字,并退出程序。

9. 与服务器通信的循环
char buffer[BUFFER_SIZE];
while (1) {
    // ...
}

定义一个循环,用于持续接收用户输入并发送数据到服务器。

10. 用户输入
printf("Enter message to send to server (or 'exit' to quit): ");
fgets(buffer, BUFFER_SIZE, stdin);

提示用户输入消息,使用fgets()函数从标准输入读取一行文本。

11. 退出条件
if (strcmp(buffer, "exit\n") == 0) {
    break;
}

如果用户输入"exit",则退出循环。

12. 发送数据到服务器
if (send(sock, buffer, strlen(buffer), 0) < 0) {
    perror("Send failed");
    break;
}

使用send()函数将用户输入的消息发送到服务器。如果发送失败,打印错误消息并退出循环。

13. 接收服务器响应
if (recv(sock, buffer, BUFFER_SIZE, 0) < 0) {
    perror("Receive failed");
    break;
}
printf("Received from server: %s", buffer);

使用recv()函数接收服务器发送的数据。如果接收失败,打印错误消息并退出循环。如果接收成功,打印接收到的消息。

14. 关闭套接字
close(sock);
printf("Disconnected from the server.\n");

在通信结束后,关闭套接字并打印断开连接的消息。

15. 正常退出
return 0;

程序正常退出,返回零值。

客户端程序是一个简单的命令行工具,允许用户与服务器进行交互,发送消息并接收回显响应。
程序通过循环接收用户输入,直到用户输入"exit"命令。
程序中包含了必要的错误处理,确保在发生错误时能够给出清晰的提示并正确地清理资源。

在这里插入图片描述

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

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

相关文章

谷歌重磅:告别RAG,长上下文的大语言模型无需检索增强

当今人工智能领域正在经历一场静默的革命。随着大语言模型(LLM)的快速发展&#xff0c;它们不仅能够处理更长的上下文&#xff0c;还展现出惊人的推理和检索能力。 难道我们要告别基于LLM的检索增强生成(RAG)了吗&#xff1f; 结果还真是这样&#xff0c;最近谷歌发布专门用于…

k8s公网集群安装(1.23.0)

网上搜到的公网搭建k8s都不太一致, 要么说的太复杂, 要么镜像无法下载, 所以写了一个简洁版,小白也能一次搭建成功 使用的都是centos7,k8s版本为1.23.0 使用二台机器搭建的, 三台也是一样的思路1.所有节点分别设置对应主机名 hostnamectl set-hostname master hostnamectl set…

一文搞懂 java 线程池:ThreadPoolExecutor 和 FixedThreadPool 原理

你好&#xff0c;我是 shengjk1&#xff0c;多年大厂经验&#xff0c;努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注&#xff01;你会有如下收益&#xff1a; 了解大厂经验拥有和大厂相匹配的技术等 希望看什么&#xff0c;评论或者私信告诉我&#xff01; 文章目录 一…

数据结构-排序算法篇

前言 在我们的生活中有很多东西都是有大小的&#xff0c;那么该如何去排序&#xff1f;假设有10个数字要你去排序&#xff0c;眼睛一扫就看出来了&#xff0c;那100、1000、10000要怎么去排&#xff1f;下面就为大家介绍各种排序的算法。 内容 1.冒泡排序 2.选择排序 3.插入…

某Dota/IM对战平台玩家助手、查看战绩下、胜率等

功能说明 WAR3游戏启动后&#xff0c;可以自动获取游戏双方的玩家列表&#xff0c;然后查询显示玩家的战绩及个人信息。附带查看玩家的战绩详情、最近游戏&#xff0c;查看对手及友方的战绩详情&#xff0c;据此推算出是否开黑、是否小号等信息 使用方法及运行效果 启动 查…

武汉星起航:贴心服务引领,跨境电商成功启航

在当今全球互联互通日益加强的背景下&#xff0c;跨境电商已经跃升为驱动国际贸易繁荣的重要引擎。作为全球电商领域的翘楚&#xff0c;亚马逊坚守公平、公正、透明的商业准则&#xff0c;为全球卖家搭建了一个值得信赖的交易平台。在这个平台上&#xff0c;众多卖家通过提升产…

如何指定Microsoft Print To PDF的输出路径

在上一篇文章中&#xff0c;介绍了三种将文件转换为PDF的方式。默认情况下&#xff0c;在Microsoft Print To PDF的首选项里&#xff0c;是看不到输出路径的设置的。 需要一点小小的手段。 运行输入 control 打开控制面板&#xff0c;选择硬件和声音下的查看设备和打印机 找到…

Django 多对多关系

多对多关系作用 Django 中&#xff0c;多对多关系模型的作用主要是为了表示两个模型之间的多对多关系。具体来说&#xff0c;多对多关系允许一个模型的实例与另一个模型的多个实例相关联&#xff0c;反之亦然。这在很多实际应用场景中非常有用&#xff0c;比如&#xff1a; 博…

Ceyear®VSA 信号分析软件

CeyearVSA 信号分析软件 CeyearVSA 矢量信号分析软件 CeyearVSA 矢量信号分析软件将信号分析体验和测试应用于桌面&#xff0c;帮助排查问题并优化设计。 CeyearVSA 矢量信号分析软件结合仪表支持在线解调分析&#xff0c;也可支持信号导入离线分析&#xff1b;软件具有多种…

搜狐新闻HarmonyOS版本 push 推送开发

背景 搜狐新闻作为HarmonyOS的合作伙伴&#xff0c;于2023年12月成功上架鸿蒙单框架应用市场&#xff0c;成为首批鸿蒙应用矩阵的一员。 新闻类推送作为应用的重要组成部分&#xff0c;在二期规划中&#xff0c;我们将推送功能列为核心功能模块。本文将推送集成过程中的步骤和…

oracle体系结构详解(实例+数据文件)

提示&#xff1a;主要总结oracle数据库&#xff1a;物理结构&#xff0c;逻辑结构&#xff0c;内存结构以及oracle进程 文章目录 Oracle服务器由&#xff08;实例和数据库文件组成&#xff09;1、实例2、数据文件1.oracle物理体系结构2.oracle数据库逻辑结构3oracle数据库内存结…

Log4j日志框架讲解(全面,详细)

Log4j概述 Log4j是Apache下的一款开源的日志框架&#xff0c;通过在项目中使用 Log4J&#xff0c;我们可以控制日志信息输出到控制台、文件、甚至是数据库中。我们可以控制每一条日志的输出格式&#xff0c;通过定义日志的输出级别&#xff0c;可以 更灵活的控制日志的输出过程…

【前端vue3】TypeScrip-类型推论和类型别名

类型推论 TypeScript里&#xff0c;在有些没有明确指出类型的地方&#xff0c;类型推论会帮助提供类型。 例如&#xff1a; 变量xiaoc被推断类型为string 如重新给xiaoc赋值数字会报错 let xiaoc "xiaoc"xiaoc 1111111111111如没有给变量指定类型和赋值&#xf…

阿里Nacos下载、安装(保姆篇)

文章目录 Nacos下载版本选择Nacos安装Windows常见问题解决 更多相关内容可查看 Nacos下载 Nacos官方下载地址&#xff1a;https://github.com/alibaba/nacos/releases 码云拉取&#xff08;如果国外较慢或者拉取超时可以试一下国内地址&#xff09; //国外 git clone https:…

RabbitMQ进阶篇

文章目录 发送者的可靠性生产者重试机制实现生产者确认 MQ的可靠性数据持久化交换机持久化队列持久化消息持久化 Lazy Queue(可配置~)控制台配置Lazy模式代码配置Lazy模式更新已有队列为lazy模式 消费者的可靠性消费者确认机制失败重试机制失败处理策略 业务幂等性唯一消息ID业…

编译libvlccpp

首先下载vlc sdk https://get.videolan.org/vlc/3.0.9.2/win64/vlc-3.0.9.2-win64.7z Cmake 生成libvlccpp vs2022工程文件 编译libvlccpp 编译出错需修改代码 错误信息&#xff1a; \VLC\sdk\include\vlc/libvlc_media.h(368): error C2065: “libvlc_media_read_cb”: 未…

Linux高并发服务器开发(九)Tcp状态转移和IO多路复用

文章目录 0 包裹函数1 多进程服务器流程代码 2 多线程服务器3 TCP状态转移半关闭心跳包 4 端口复用5 IO多路复用技术高并发服务器 6 select代码总结 7 POLLAPI代码poll相对select的优缺点 8 epoll&#xff08;重点&#xff09;API监听管道代码EPOLL 高并发服务器 9 Epoll的两种…

【MySQL备份】Percona XtraBackup加密备份实战篇

目录 1.前言 2.准备工作 2.1.环境信息 2.2.配置/etc/my.cnf文件 2.3.授予root用户BACKUP_ADMIN权限 2.4.生成加密密钥 2.5.配置加密密钥文件 3.加密备份 4.优化加密过程 5.解密加密备份 6.准备加密备份 7.恢复加密备份 7.1.使用rsync进行恢复 7.2.使用xtrabackup命令恢…

go Channel原理 (四)

Channel 设计原理 不要通过共享内存的方式进行通信&#xff0c;而是应该通过通信的方式共享内存。 在主流编程语言中&#xff0c;多个线程传递数据的方式一般都是共享内存。 Go 可以使用共享内存加互斥锁进行通信&#xff0c;同时也提供了一种不同的并发模型&#xff0c;即通…

终极指南:RNNS、Transformers 和 Diffusion 模型

一、说明 作为广泛使用这些工具和模型的人&#xff0c;我的目标是解开 RNN、Transformer 和 Diffusion 模型的复杂性和细微差别&#xff0c;为您提供详细的比较&#xff0c;为您的特定需求提供正确的选择。 无论您是在构建语言翻译系统、生成高保真图像&#xff0c;还是处理时间…