Linux系统编程之多线程

news2024/9/26 5:21:55

目录

1、进程与线程

2、线程的优势与使用理由

3、多线程的使用

3.1 线程的使用

1.线程创建函数

 2.线程退出函数

3.线程的等待

4.线程脱离

5. 线程ID获取及比较

 6.示例

 3.2 互斥锁的应用

1.互斥锁相关API

2.示例

3.3 条件变量的使用

1. 创建及销毁条件变量

2. 等待

3. 触发

4.示例


1、进程与线程

Linux 线程是在 Linux 操作系统中运行的基本执行单元。线程是进程的一部分,共享同一地址空间和其他资源,但拥有独立的执行流。在 Linux 中,线程被称为轻量级进程(LWP,Lightweight Process),但轻量级进程更多指的是内核线程(kernel thread),而把用户线程(user thread)称为线程。

"进程——资源分配的最小单位,线程——程序执行的最小单位"

进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。 

2、线程的优势与使用理由

从上面我们大概了解了进程与线程的区别,使用多线程的理由主要包括资源节俭、方便的通信机制、提高应用程序响应性、有效利用多CPU系统以及改善程序结构。这些优点使得多线程成为处理并发任务的一种强大工具,特别是在需要高效利用系统资源和提高程序性能的场景下。当然,使用多线程也需要注意线程安全性和避免潜在的并发问题。

这部分摘抄于网络:

使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。

  使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。

  除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:

  • 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
  • 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
  • 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

3、多线程的使用

多线程开发在Linux开发平台上已经有成熟的pthread库支持,详细请见下表:

3.1 线程的使用

1.线程创建函数

#include <pthread.h>  注意都是调用这个库

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,

                                 void *(*start_routine)(void *), void *arg);

参数说明:

  • thread:指向新创建的线程标识符的指针,该标识符用于标识新线程。
  • attr:指向线程属性的指针,用于指定新线程的属性,通常可以设置为 NULL,表示使用默认属性。
  • start_routine:是一个函数指针,指向新线程将要执行的函数。该函数应该接受一个 void* 类型的参数并返回一个 void* 类型的指针。
  • arg:是传递给 start_routine 函数的参数。如果需要向start_routine函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。

pthread_create 的返回值为 0 表示成功创建线程,非零表示创建线程失败,返回的错误码可以用 errno 查看。

 2.线程退出函数

单个线程可以通过以下三种方式退出,在不终止整个进程的情况下停止它的控制流:

  1)线程只是从启动例程中返回,返回值是线程的退出码。

  2)线程可以被同一进程中的其他线程取消。

  3)线程调用pthread_exit:

函数原型

void pthread_exit(void *retval);
retval是一个无类型指针,线程可以选择在执行过程中退出,并提供一个退出状态。这个状态可以在其他线程中通过调用 pthread_join 来获取,用于了解线程的退出状态。

使用这个函数可以确保线程资源的正确清理。

3.线程的等待

函数原型

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

  • pthread_t thread 是要等待的线程的标识符。
  • void **retval 是一个指针,用于存储线程的退出状态。这个参数可以为 NULL,表示不关心线程的退出状态。

pthread_join 函数的返回值为 0 表示成功等待线程结束,非零表示等待失败。如果成功,线程的退出状态将存储在 retval 指向的地址中

4.线程脱离

一个线程或者是可汇合(joinable,默认值),或者是脱离的(detached)。当一个可汇合的线程终止时,它的线程ID和退出状态将留存到另一个线程对它调用pthread_join。脱离的线程却像守护进程,当它们终止时,所有相关的资源都被释放,我们不能等待它们终止,使用pthread_detach函数可以把一个线程标记为脱离状态。如果一个线程需要知道另一线程什么时候终止,那就最好保持第二个线程的可汇合状态。

函数原型

int pthread_detach(pthread_t thread);

  • pthread_t thread 是要设置为可分离状态的线程的标识符。
  • pthread_detach 函数的返回值为 0 表示成功,非零表示失败。
5. 线程ID获取及比较

pthread_t pthread_self(void);

// 返回:调用线程的ID

对于线程ID比较,为了可移植操作,我们不能简单地把线程ID当作整数来处理,因为不同系统对线程ID的定义可能不一样。我们应该要用下边的函数:

 int pthread_equal(pthread_t tid1, pthread_t tid2);

//用于比较两个线程的标识符是否相等

// 返回:若相等则返回非0值,否则返回0

 6.示例
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define THREAD_EXIT_STATUS "happy"

void *start_rtn(void *arg) {
    int data = *((int *)arg);
    printf("data = %d\n", data);
    printf("pthread_pid = %ld\n", pthread_self());

    // 注意:如果线程函数返回的字符串是动态分配的内存,需要在主线程中释放
    //strdup:用于创建一个字符串的副本并返回指向这个副本的指针。
    char *exit_status = strdup(THREAD_EXIT_STATUS);
    pthread_exit((void *)exit_status);
}

int main() {
    pthread_t pthread_pid;
    int dis = 100;
    char *exit_status = NULL;

    // 添加错误处理
    if (pthread_create(&pthread_pid, NULL, start_rtn, (void *)&dis) != 0) {
        perror("Thread creation failed");
        return 1;
    }

    // 添加错误处理
    if (pthread_join(pthread_pid, (void **)&exit_status) != 0) {
        perror("Thread join failed");
        return 1;
    }

    printf("main - - data  = %s\n", exit_status);

    // 释放动态分配的内存
    free(exit_status);

    printf("main pthread_pid = %ld\n", pthread_self());

    return 0;
}

 3.2 互斥锁的应用

互斥锁(Mutex,全名为 Mutual Exclusion)是一种用于多线程编程的同步机制,用于保护共享资源,防止多个线程同时访问或修改这些资源。互斥锁的基本思想是一次只允许一个线程进入临界区(临界区是指访问共享资源的代码段),其他线程需要等待当前线程释放锁后才能进入。互斥变量用pthread_mutex_t数据类型表示。在使用互斥变量前必须对它进行初始化,可以把它置为常量PTHREAD_MUTEX_INITIALIZER(只对静态分配的互斥量),也可以通过调用pthread_mutex_init函数进行初始化。如果动态地分配互斥量(例如通过调用malloc函数),那么在释放内存前需要调用pthread_mutex_destroy。

1.互斥锁相关API
//创建互斥锁对象,PTHREAD_MUTEX_INITIALIZER是一个宏,用于静态初始化互斥锁。在声明互斥锁变量时,可以使用这个宏进行初始化,而无需调用 pthread_mutex_init
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
//初始化互斥锁,第二个参数是 NULL 的话,互斥锁的属性会设置为默认属性
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
//阻塞调用,如果这个锁此时正在被其它线程占用, 那么 pthread_mutex_lock() 调用会进入到这个锁的排队队列中,并会进入阻塞状态, 直到拿到锁之后才会返回。
int pthread_mutex_lock(pthread_mutex_t *mutex);
//非阻塞调用,当请求的锁正砸被其他线程占用时, 不会进入阻塞状态,而是立刻返回,并返回一个错误代码 EBUSY,意思是说, 有其它线程正在使用这个锁。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
//释放锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//在指定时间内等待互斥锁,超时返回错误
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abs_timeout);
2.示例
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>

int dis = 0;
pthread_mutex_t mutex;

// 线程1的执行函数
void *func1(void *data) {
    static int ret = 10;
    while (1) {
        pthread_mutex_lock(&mutex);
        printf("func1: %ld\n", pthread_self());
        printf("func1: %s\n", (char *)data);
        printf("func1 -> dis = %d\n", dis++);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
    pthread_exit((void *)&ret);
}

// 线程2的执行函数
void *func2(void *data) {
    static int ret = 20;
    while (1) {
        pthread_mutex_lock(&mutex);
        printf("func2: %ld\n", pthread_self());
        printf("func2: %s\n", (char *)data);
        printf("func2 -> dis = %d\n", dis++);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
    pthread_exit((void *)&ret);
}

// 线程3的执行函数
void *func3(void *data) {
    static int ret = 30;
    while (1) {
        pthread_mutex_lock(&mutex);
        printf("func3: %ld\n", pthread_self());
        printf("func3: %s\n", (char *)data);
        printf("func3 -> dis = %d\n", dis++);
        pthread_mutex_unlock(&mutex);
        if (dis == 5) {
        //当dis加到5时线程3会退出,其他线程继续循环
            pthread_exit((void *)&ret);
        }
    }
}

int main() {
    pthread_t tidp1, tidp2, tidp3;
    char *arg = "happy bay!!!";
    int *ret;

    // 初始化互斥锁
    pthread_mutex_init(&mutex, NULL);

    // 创建三个线程
    pthread_create(&tidp1, NULL, func1, (void *)arg);
    pthread_create(&tidp2, NULL, func2, (void *)arg);
    pthread_create(&tidp3, NULL, func3, (void *)arg);

    while (1) {
        printf("main -> dis = %d\n", dis++);
        sleep(1);
    }

    // 等待线程1结束
    pthread_join(tidp1, (void **)&ret);
    printf("Thread 1 returned: %d\n", *ret);

    // 等待线程2结束
    pthread_join(tidp2, (void **)&ret);
    printf("Thread 2 returned: %d\n", *ret);

    // 等待线程3结束
    pthread_join(tidp3, (void **)&ret);
    printf("Thread 3 returned: %d\n", *ret);

    // 销毁互斥锁
    pthread_mutex_destroy(&mutex);

    return 0;
}

3.3 条件变量的使用

条件变量是线程另一可用的同步机制。条件变量给多个线程提供了一个会合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。条件本身是由互斥量保护的。线程在改变条件状态前必须首先锁住互斥量,其他线程在获得互斥量之前不会察觉到这种改变,因为必须锁定互斥量以后才能计算条件。条件变量使用之前必须首先初始化,pthread_cond_t数据类型代表的条件变量可以用两种方式进行初始化,可以把常量PTHREAD_COND_INITIALIZER赋给静态分配的条件变量,但是如果条件变量是动态分配的,可以使用pthread_cond_destroy函数对条件变量进行去除初始化(deinitialize)。

1. 创建及销毁条件变量

#include <pthread.h>
//初始化条件变量
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
//销毁条件变量
int pthread_cond_destroy(pthread_cond_t cond);
// 返回:若成功返回0,否则返回错误编号

除非需要创建一个非默认属性的条件变量,否则pthread_cont_init函数的attr参数可以设置为NULL

2. 等待
#include <pthread.h>
//等待条件变量满足,同时释放互斥锁,使得其他线程可以获取互斥锁
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, cond struct timespec *restrict timeout);
// 返回:若成功返回0,否则返回错误编号

pthread_cond_wait等待条件变为真。如果在给定的时间内条件不能满足,那么会生成一个代表一个出错码的返回变量。传递给pthread_cond_wait的互斥量对条件进行保护,调用者把锁住的互斥量传给函数。函数把调用线程放到等待条件的线程列表上,然后对互斥量解锁,这两个操作都是原子操作。这样就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道,这样线程就不会错过条件的任何变化。pthread_cond_wait返回时,互斥量再次被锁住。

pthread_cond_timedwait函数的工作方式与pthread_cond_wait函数类似,只是多了一个timeout。timeout指定了等待的时间,它是通过timespec结构指定。

3. 触发

#include <pthread.h>
//唤醒等待条件变量的一个线程
int pthread_cond_signal(pthread_cond_t cond);
//唤醒等待条件变量的所有线程
int pthread_cond_broadcast(pthread_cond_t cond);
// 返回:若成功返回0,否则返回错误编号

这两个函数可以用于通知线程条件已经满足。pthread_cond_signal函数将唤醒等待该条件的某个线程,而pthread_cond_broadcast函数将唤醒等待该条件的所有进程。

  注意一定要在改变条件状态以后再给线程发信号。

4.示例
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
//初值为一个包含所有字段为 0 的结构体
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int shared_data = 0;

void *producer(void *arg) {
    while (1) {
        pthread_mutex_lock(&mutex);

        // 生产者线程更新共享资源
        shared_data++;
        printf("Producer produced item: %d\n", shared_data);

        // 通知等待条件的线程
        pthread_cond_signal(&cond);

        pthread_mutex_unlock(&mutex);

        // 模拟生产过程
        sleep(1);
    }
}

void *consumer(void *arg) {
    while (1) {
        pthread_mutex_lock(&mutex);

        // 检查条件,如果条件不满足,则等待
        while (shared_data == 0) {
            pthread_cond_wait(&cond, &mutex);
        }

        // 消费者线程消耗共享资源
        printf("Consumer consumed item: %d\n", shared_data);
        shared_data--;

        pthread_mutex_unlock(&mutex);

        // 模拟消费过程
        sleep(1);
    }
}

int main() {
    pthread_t producer_tid, consumer_tid;

    // 创建生产者线程和消费者线程
    pthread_create(&producer_tid, NULL, producer, NULL);
    pthread_create(&consumer_tid, NULL, consumer, NULL);

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

    // 销毁互斥锁和条件变量
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);

    return 0;
}

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

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

相关文章

数字化转型导师坚鹏:证券公司数字化领导力提升之道

证券公司数字化领导力提升之道 ——融合中西智慧&#xff0c;践行知行合一思想&#xff0c;实现知行果合一 课程背景&#xff1a; 很多证券公司存在以下问题&#xff1a; 不知道证券公司数字化思维如何提升&#xff1f; 不清楚证券公司数字化领导力模型内涵&#xff1f;…

加密与安全_探索数字证书

文章目录 Pre概述使用keytool生成证书使用Openssl生成证书 &#xff08;推荐&#xff09;证书的吊销小结 Pre PKI - 借助Nginx 实现Https 服务端单向认证、服务端客户端双向认证 PKI - 04 证书授权颁发机构&#xff08;CA&#xff09; & 数字证书 PKI - 数字签名与数字证…

土壤侵蚀量化评估

根据之前的文章,已经算出了R、K、LS、C、P 现在计算土壤侵蚀,将几个前期制作好的因子的TIFF文件,用栅格计算器相乘 发现局部地区存在轻度侵蚀,大部分区域是微度侵蚀 然后对比了一下范围 其中的几个因子都在文献范围内,说明计算结果并未出错,可能就是研究区正常范围和结…

《数字图像处理(MATLAB版)》相关算法代码及其分析(1)

目录 1 自适应中值滤波算法 1.1 函数定义 1.2 输入参数检查 1.3 初始化 1.4 自适应中值滤波过程 1.5 处理剩余未处理的像素 1.6 总结 2 计算输入数组的平均值 2.1 函数定义 2.2 注释 2.3 输入验证 2.4 计算平均值 2.5 总结 3 基于高斯模型的贝叶斯分类器 3.1 函…

【搭建 Hbase 集群】

搭建 Hbase 集群 一、准备工作二、三台服务器之间的 SSH 免密登录1.修改hosts文件添加DNS映射2.在每台服务器上生成 SSH 密钥对3.将公共密钥&#xff08;通常为 ~/.ssh/id_rsa.pub&#xff09;复制到目标服务器上4.从本地使用 SSH 命令无需密码连接到目标服务器 二、安装JDK1.执…

Linux/Docker 修改系统时区

目录 1. Linux 系统1.1 通过 timedatectl 命令操作1.2 直接修改 /etc/localtime 文件 2. Docker 容器中的 Linux 操作环境&#xff1a; CentOS / AlmaOSMySQL Docker 镜像 1. Linux 系统 1.1 通过 timedatectl 命令操作 使用 timedatectl list-timezones 命令列出可用的时区…

Learning from Unlabeled 3D Environments forVision-and-Language Navigation

这篇论文是关于高级指令的 摘要 在视觉和语言导航 (VLN) 中&#xff0c;实体代理需要按照自然语言指令在真实的 3D 环境中进行导航。现有 VLN 方法的一个主要瓶颈是缺乏足够的训练数据&#xff0c;导致对未见过的环境的泛化效果不理想。虽然 VLN 数据通常是手动收集的&#x…

2024年 前端JavaScript Web APIs 第一天 笔记

1.1 -声明变量const优先 1.2 -DOM树和DOM对象 1.3 -获取DOIM元素 1.4 -DOM修改元素内容以及年会抽奖 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content&quo…

初识面相对象深入理解、构造方法--学习JavaEE的day12

day12 一、初识面相对象深入理解 需求&#xff1a; 创建人类的对象&#xff0c;并操作对象 分析&#xff1a; 人类 - Person 属性&#xff1a;name、sex、age 方法&#xff1a;eat、sleep 场景&#xff1a;创建多个对象&#xff0c;去操作对象 public class Person {//成员变…

9、taocms代码审计

一、XSS 1、DOM型xss 限制 无复现 payload: aa)alert(1)( 触发的参数&#xff1a;name代码 根据路由找到对应的文件&#xff0c;在api.php里接受全局变量action&#xff0c;最终赋值给$m,判断 如果$m不在数组就结束&#xff0c;新建方法复制给$model。检查类的方法是否存…

2024 年广东省职业院校技能大赛(高职组)“云计算应用”赛项样题 2

#需要资源或有问题的&#xff0c;可私博主&#xff01;&#xff01;&#xff01; #需要资源或有问题的&#xff0c;可私博主&#xff01;&#xff01;&#xff01; #需要资源或有问题的&#xff0c;可私博主&#xff01;&#xff01;&#xff01; 某企业根据自身业务需求&#…

【全局异常处理记录】⭐️通过自定义全局处理器有效统一各种异常并记录

目录 前言 方案 示例 测试 总结 前言 朋友们大家好啊&#xff0c;随着项目的进行&#xff0c;接口也是越来越多了&#xff0c;每个接口无论调用成功与否&#xff0c;都要有相应的应对措施&#xff0c;总不能出错的时候返回一堆异常信息给调用者&#xff0c;所以每个接口都…

Python算法100例-3.2 水仙花数

完整源代码项目地址&#xff0c;关注博主私信源代码后可获取 1.问题描述2.问题分析3.算法设计4.确定程序框架5.完整的程序6.问题拓展7.巧用字符串技巧 1&#xff0e;问题描述 输出所有的“水仙花数”。所谓的“水仙花数”是指一个三位数&#xff0c;其各位数字的立方和等于该…

【机器学习】有监督学习算法之:支持向量机

支持向量机 1、引言2、决策树2.1 定义2.2 原理2.3 实现方式2.4 算法公式2.5 代码示例 3、总结 1、引言 小屌丝&#xff1a;鱼哥&#xff0c;泡澡啊。 小鱼&#xff1a;不去 小屌丝&#xff1a;… 此话当真&#xff1f; 小鱼&#xff1a;此话不假 小屌丝&#xff1a;到底去还是…

奔跑吧,前端er!前端五大方向技能罗列,webGL、AI、桌面、游戏

经常看到头条上前端们争论各种框架的优劣&#xff0c;然后相互争吵不休&#xff0c;其实技术也好&#xff0c;框架也好&#xff0c;都是服务于项目需求的&#xff0c;争论的铁子们都站在自己的项目角度来品评工具&#xff0c;肯定是公说公有理婆说婆有理啦。 技术和框架是中性的…

ArrayBlockingQueue 数组阻塞队列 源码阅读

1. 概述 数组阻塞队列 有界的阻塞数组, 容量一旦创建, 无法修改阻塞队列, 队列满的时候, 往队列put数据会被阻塞, 队列空, 取数据也会被阻塞并发安全 2. 数据结构 /** 存储队列元素的数组 */ /** 存储队列元素的数组 */ final Object[] items;/** 队首位置&#xff0c;下一…

【王道操作系统】ch1计算机系统概述-06虚拟机

文章目录 【王道操作系统】ch1计算机系统概述-06虚拟机01传统计算机02虚拟机的基本概念&#xff08;1&#xff09;第一类虚拟机管理程序&#xff08;2&#xff09; 第二类虚拟机管理程序&#xff08;3&#xff09; 两类虚拟机管理程序的对比 【王道操作系统】ch1计算机系统概述…

【Linux系统化学习】线程概念

目录 线程的概念 线程的引出 什么是线程 理解线程比进程更加的轻量化 线程的优点 现成的缺点 线程异常 线程用途 Linux进程VS线程 线程的简单现象 线程的概念 有关操作系统的书籍或者课本都会这样描述线程&#xff1a; 线程是比进程轻量化的一种执行流线程是进程内部…

[SWPUCTF 2021 新生赛]babyrce

先打开环境 分析代码&#xff0c;要给COOKIE赋值admin1 使用hackbar赋值 打开rasalghul.php 分析代码&#xff0c;使用GET传参url&#xff0c;如果url里没有/ /&#xff0c;则赋值给ip&#xff0c;然后通过shell_exec函数得到flag&#xff0c;否则&#xff0c;返回nonono。他…

备战蓝桥杯————递归反转单链表

当要求只反转单链表中的一部分时&#xff0c;递归实现确实具有一定的挑战性&#xff0c;但也是可行的。下面我将介绍一种递归实现的方法来反转单链表中的一部分。 一、反转链表 题目描述 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示…