Linux系统之 — 线程

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.mfbz.cn/a/765786.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Keil5 ST-LINK setting闪退问题解决

1. 官网下载新版驱动文件 MDK uVision crashes when using ST-Link debugger 2. 解压替换 STLinkUSBDriver6.1.2.0Signed 我的库文件目录&#xff1a; D:\Tool\Keil5\ARM\STLink

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

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

Linux——shell原理和文件权限

1.shell原理 在我们使用云服务器时&#xff0c;需要通过shell进行使用&#xff0c;而shell则是一种外壳程序。 我们提到过&#xff0c;大部分的指令实际上就是文件&#xff0c;当用户需要执行某种功能时&#xff0c;由于用户不擅长和操作系统直接交互&#xff08;操作复杂&…

k8s部署单机版mysql8

一、创建命名空间 # cat mysql8-namespace.yaml apiVersion: v1 kind: Namespace metadata:name: mysql8labels:name: mysql8# kubectl apply -f mysql8-namespace.yaml namespace/mysql8 created# kubectl get ns|grep mysql8 mysql8 Active 8s二、创建mysql配…

论文学习——使用基于多项式拟合的预测算法求解动态多目标问题

论文题目&#xff1a;Solving dynamic multi-objective problems using polynomial fitting-based prediction algorithm 使用基于多项式拟合的预测算法求解动态多目标问题&#xff08;Qingyang Zhang , Xiangyu He,Shengxiang Yang , Yongquan Dong , Hui Song , Shouyong Ji…

配置WLAN 示例

规格 仅AR129CVW、AR129CGVW-L、AR109W、AR109GW-L、AR161W、AR161EW、AR161FGW-L、AR161FW、AR169FVW、AR169JFVW-4B4S、AR169JFVW-2S、AR169EGW-L、AR169EW、AR169FGW-L、AR169W-P-M9、AR1220EVW和AR301W支持WLAN-FAT AP功能。 组网需求 如图1所示&#xff0c;企业使用WLAN…

JDeveloper 12C 官网下载教程

首先、我们要登录Oracle官网 Oracle 甲骨文中国 | 云应用和云平台 登录进去如果不是中文可以点击右上角带有国旗的图标就行更改&#xff0c;选择一个你能看懂的文字。 然后&#xff0c;点击“资源”—点击“开发人员下载” 然后&#xff0c;点击“开发工具” 这里有很多工具可…

【设计模式】【行为型模式】【责任链模式】

系列文章目录 可跳转到下面链接查看下表所有内容https://blog.csdn.net/handsomethefirst/article/details/138226266?spm1001.2014.3001.5501文章浏览阅读2次。系列文章大全https://blog.csdn.net/handsomethefirst/article/details/138226266?spm1001.2014.3001.5501 目录…

Python协作运动机器人刚体力学解耦模型

&#x1f3af;要点 &#x1f3af;腿式或固定式机器人模型 | &#x1f3af;网格、点云和体素网格碰撞检测 | &#x1f3af;正反向运动学和动力学 | &#x1f3af;机器人刚体力学计算 | &#x1f3af;编辑参考系姿势和路径 | &#x1f3af;软件接口实体机器人模拟 | &#x1f3a…

奇葩公司又发微博了,网友表示“乐”

多益网络 近日&#xff0c;多益网络官方微博发帖&#xff0c;公然表示对法院仲裁结果不服&#xff0c;认为劳动法有极多问题。 大家不要看微博内容似乎振振有词&#xff0c;极有可能只是多益网络单方面的选择性表达&#xff0c;毕竟多益网络的臭名早就家喻户晓。 况且对前员工直…

车载资料分享中:硬件在环、canoe、UDS诊断、OTA升级、TBOX测试

每日直播时间&#xff1a; 周一到周五&#xff1a;20&#xff1a;00-23&#xff1a;00 周六与周日&#xff1a;9&#xff1a;00-17&#xff1a;00 直播内容&#xff1a;&#xff08;车厂一比一测试&#xff09; HIL&#xff08;硬件在环&#xff09;测试、UDS功能诊断、UDS自动…

一首歌的时间 写成永远

大家好&#xff0c;我是秋意零。 就在&#xff0c;2024年6月20日。我本科毕业了&#xff0c;之前专科毕业挺有感触&#xff0c;也写了一篇文章进行记录。如今又毕业了&#xff0c;还是写一篇文章记录吧&#xff01;&#xff01; 专科毕业总结&#xff1a;大学三年总结&#xf…

使用ElementUI组件库

引入ElementUI组件库 1.安装插件 npm i element-ui -S 2.引入组件库 import ElementUI from element-ui; 3.引入全部样式 import element-ui/lib/theme-chalk/index.css; 4.使用 Vue.use(ElementUI); 5.在官网寻找所需样式 饿了么组件官网 我这里以button为例 6.在组件中使用…

利用深度学习模型进行语音障碍自动评估

语音的产生涉及器官的复杂协调&#xff0c;因此&#xff0c;语音包含了有关身体各个方面的信息&#xff0c;从认知状态和心理状态到呼吸条件。近十年来&#xff0c;研究者致力于发现和利用语音生物标志物——即与特定疾病相关的语音特征&#xff0c;用于诊断。随着人工智能&…

电信NR零流量小区处理

【摘要】随着目前网络建设逐步完善&#xff0c;5G用户的不断发展&#xff0c;针对零流量小区的分析及处理存在着必要性&#xff0c;零流量小区的出现既是用户分布及行为的直观体现&#xff0c;也是发展用户的一个指引&#xff0c;同时也能发现设备的一些故障。一个站点的能够带…

飞书API 2-3:如何使用 API 创建数据表,解放人工?

一、引入 作为飞书多维表的深度使用者&#xff0c;经常需要将一些数据库的数据同步到多维表上&#xff0c;在数据写入之前&#xff0c;一般需要新建数据表和字段。当通过网页端界面新建字段时&#xff0c;如果字段少&#xff0c;还能接受手动一个个创建&#xff0c;不过一旦字…

C++字体库开发

建议根据字体需求&#xff0c;多个组合使用。高度定制可基于freeTypeharfbuzz基础库完成。 GitHub - GNOME/pango: Read-only mirror of https://gitlab.gnome.org/GNOME/pango GitHub - googlefonts/fontview: Demo app that displays fonts with a free/libre/open-source …

更好的方法_交叉观察器API

交叉观察器&#xff08;Intersection Observer&#xff09;API 是一个强大的工具&#xff0c;可以用来检测元素是否进入视口或从视口移出。我们可以利用这个 API 来实现粘贴式导航&#xff08;也称为粘性导航&#xff09;&#xff0c;即在用户滚动页面时&#xff0c;导航栏会在…

方法的用法

一.简介 目前为止我给出的所有的案例都是将代码放在main方法中&#xff0c;就会产生一些问题&#xff1a; 代码冗长&#xff0c;不利于维护变量过多&#xff0c;想不出那么多的变量名没有重用性 那么该如何解决呢&#xff1f; 我们可以编写功能性的代码块&#xff0c;来被ma…

Android自动化测试实践:uiautomator2 核心功能与应用指南

Android自动化测试实践&#xff1a;uiautomator2 核心功能与应用指南 uiautomator2 是一个用于Android应用的自动化测试Python库&#xff0c;支持多设备并行测试操作。它提供了丰富的API来模拟用户对App的各种操作&#xff0c;如安装、卸载、启动、停止以及清除应用数据等。此外…