【lesson53】线程控制

news2024/11/28 23:47:25

文章目录

  • 线程控制

线程控制

线程创建
代码:
在这里插入图片描述
运行代码:
在这里插入图片描述
强调一点,线程和进程不一样,进程有父进程的概念,但在线程组里面,所有的线程都是对等关系。
在这里插入图片描述
错误检查:

  • 传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。
  • pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误代码通过返回值返回
  • pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值业判定,因为读取返回值要比读取线程内的errno变量的开销更小

进程ID和线程ID
在Linux中,目前的线程实现是Native POSIX Thread Libaray,简称NPTL。在这种实现下,线程又被称为轻量级进程(Light Weighted Process),每一个用户态的线程,在内核中都对应一个调度实体,也拥有自己的进程描述符(task_struct结构体)。
没有线程之前,一个进程对应内核里的一个进程描述符,对应一个进程ID。但是引入线程概念之后,情况发生了变化,一个用户进程下管辖N个用户态线程,每个线程作为一个独立的调度实体在内核态都有自己的进程描述符,进程和内核的描述符一下子就变成了1:N关系,POSIX标准又要求进程内的所有线程调用
getpid函数时返回相同的进程ID,如何解决上述问题呢?
Linux内核引入了线程组的概念。
在这里插入图片描述
多线程的进程,又被称为线程组,线程组内的每一个线程在内核之中都存在一个进程描述符(task_struct)与之对应。进程描述符结构体中的pid,表面上看对应的是进程ID,其实不然,它对应的是线程ID;进程描述
符中的tgid,含义是Thread Group ID,该值对应的是用户层面的进程ID
在这里插入图片描述

线程异常
我们之前学到线程一旦异常那么整个进程都会退出,那么真的是如此吗?
演示:
代码

#include <iostream>
#include <pthread.h>
#include <unistd.h>

void *threadRoutine(void *arg)
{
    const std::string name = (char *)arg;
    while (true)
    {
        std::cout << name << " pid:" << getpid() << "\n"
                  << std::endl;
        
        int a = 100;
        a /= 0;//除0错误
        sleep(1);
    }

    return nullptr;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");

    while (true)
    {
        std::cout << "main thread pid:" << getpid() << std::endl;
        sleep(3);
    }
    return 0;
}

运行代码:
在这里插入图片描述
我们发现线程一旦异常确实会影响到整个进程。

结论:
1.线程谁先运行与调度器相关
2.随便哪个线程一旦异常,都可能导致整个进程整体退出
3.线程在创建并执行的时候,线程也需要进行等待的,如果主线程不等待,也会引起类似于僵尸进程问题,导致内存泄漏。

线程等待
已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
创建新的线程不会复用刚才退出线程的地址空间。
在这里插入图片描述
调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:

  1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
  2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_CANCELED。
  3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
  4. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。

在这里插入图片描述

在这里插入图片描述
参数解释:
thread:线程id
retval:输出型参数,下面再解释用处
pthread_join默认阻塞等待。
代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>

void *threadRoutine(void *arg)
{
    const std::string name = (char *)arg;
    int i = 0;
    while (true)
    {
        std::cout << name << "runing....." << std::endl;
        sleep(1);

        if(i++ == 10)
        {
            break;
        }
    }

    std::cout << "new thread quit....." << std::endl;
    return nullptr;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");

    pthread_join(tid,nullptr);
    std::cout << "main thread wait done .... main quit!" << std::endl;

    return 0;
}

运行代码:
在这里插入图片描述
我们知道pthread_create里面有一个回调函数,而回调函数里面有一个返回值我们之前一直返回nullptr
在这里插入图片描述
这个返回值,一般是给主线程的,那么主线程该如何获取到?用pthread_join。
在这里插入图片描述
pthread_join的第二个参数,是输出型参数,用来获取放回值的。
代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>

void *threadRoutine(void *arg)
{
    const std::string name = (char *)arg;
    int i = 0;
    while (true)
    {
        std::cout << name << "runing....." << std::endl;
        sleep(1);

        if (i++ == 10)
        {
            break;
        }
    }

    std::cout << "new thread quit....." << std::endl;
    return (void *)10;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");

    void *ret = nullptr;
    pthread_join(tid, &ret);
    std::cout << "ret: " << (long long)ret << std::endl;
    std::cout << "main thread wait done .... main quit!" << std::endl;

    return 0;
}

我们运行的时候会这样
我们只要在g++后面加-fpermissive即可

g++ -o mythread mythread.cc -std=c++11 -lpthread -fpermissive

在这里插入图片描述
再运行代码:
在这里插入图片描述
可以看到,我们成功获取到了返回值。
我们不仅仅只能返回变量,我们还能返回其它内容。
代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>

void *threadRoutine(void *arg)
{
    const std::string name = (char *)arg;
    int i = 0;
    int *data = new int[10];
    while (true)
    {
        std::cout << name << "runing....." << std::endl;
        sleep(1);

        data[i] = i;
        if (i++ == 10)
        {
            break;
        }
    }

    std::cout << "new thread quit....." << std::endl;
    return (void *)data;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");

    void *ret = nullptr;
    pthread_join(tid, &ret);

    int *data = (int *)ret;

    for (int i = 0; i < 10; i++)
    {
        std::cout << data[i] << " ";
    }
    std::cout << std::endl;
    std::cout << "main thread wait done .... main quit!" << std::endl;
    return 0;
}

运行结果:
在这里插入图片描述
线程终止
如果需要只终止某个线程而不终止整个进程,可以有三种方法:

  1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
  2. 线程可以调用pthread_ exit终止自己。
  3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。
    能不能用exit终止线程呢?

代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>

void *threadRoutine(void *arg)
{
    const std::string name = (char *)arg;
    int i = 0;
    int *data = new int[10];
    while (true)
    {
        std::cout << name << "runing....." << std::endl;
        sleep(1);

        data[i] = i;
        if (i++ == 10)
        {
            break;
        }
    }

    std::cout << "new thread quit....." << std::endl;
    exit(10);
    return (void *)data;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");

    void *ret = nullptr;
    pthread_join(tid, &ret);

    int *data = (int *)ret;

    for (int i = 0; i < 10; i++)
    {
        std::cout << data[i] << " ";
    }
    std::cout << std::endl;
    std::cout << "main thread wait done .... main quit!" << std::endl;

    return 0;
}

运行结果:
在这里插入图片描述
我们发现整个进程都被终止了,因为exit是终止进程的,绝对不要用exit终止线程。
那么我们如何终止新线程而不影响main线程呢?
pthread_exit()OS提供的终止线程的函数
在这里插入图片描述
在这里插入图片描述
参数retval就是之前的返回值。
代码

#include <iostream>
#include <pthread.h>
#include <unistd.h>

void *threadRoutine(void *arg)
{
    const std::string name = (char *)arg;
    int i = 0;
    int *data = new int[10];
    while (true)
    {
        std::cout << name << "runing....." << std::endl;
        sleep(1);

        data[i] = i;
        if (i++ == 10)
        {
            break;
        }
    }

    std::cout << "new thread quit....." << std::endl;
    pthread_exit((void*)data);
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");

    void *ret = nullptr;
    pthread_join(tid, &ret);

    int *data = (int *)ret;

    for (int i = 0; i < 10; i++)
    {
        std::cout << data[i] << " ";
    }
    std::cout << std::endl;
    std::cout << "main thread wait done .... main quit!" << std::endl;
    return 0;
}

运行结果:
在这里插入图片描述
我们看到线程终止成功。

线程取消
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>

void *threadRoutine(void *arg)
{
    const std::string name = (char *)arg;
    while (true)
    {
        std::cout << name << "runing....." << std::endl;
        sleep(1);
    }

    std::cout << "new thread quit....." << std::endl;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");

    int count = 3;
    while (true)
    {
        std::cout << "main thread pid:" << getpid() << std::endl;
        if(count++ > 5) break;
        sleep(2);
    }

    pthread_cancel(tid);
    std::cout << "pthread cancle tid: " << tid << std::endl; 

    void *ret = nullptr;
    pthread_join(tid, &ret);

    std::cout << "ret: " << (long long)ret << std::endl;
    std::cout << "main thread wait done .... main quit!" << std::endl;
    sleep(5);

    return 0;
}

运行结果:
在这里插入图片描述
我们看到最后main线程确实等待了5秒
在这里插入图片描述
然后退出了。
我们看到其中tid为啥这么大呢?之后再讲解。
而我们看到线程被取消,我们join的时候,退出码是-1.
而-1其实是:
在这里插入图片描述

线程ID的探索
我们之前看到线程ID是一个很大的值
格式化输出线程ID:
代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <cstdio>

void *threadRoutine(void *arg)
{
    const std::string name = (char *)arg;
    while (true)
    {
        std::cout << name << "runing....." << std::endl;
        sleep(1);
    }

    std::cout << "new thread quit....." << std::endl;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");

    printf("%u,%p\n",tid,tid);
    int count = 3;
    while (true)
    {
        std::cout << "main thread pid:" << getpid() << std::endl;
        if(count++ > 5) break;
        sleep(2);
    }

    pthread_cancel(tid);
    std::cout << "pthread cancle tid: " << tid << std::endl; 

    void *ret = nullptr;
    pthread_join(tid, &ret);

    std::cout << "ret: " << (long long)ret << std::endl;
    std::cout << "main thread wait done .... main quit!" << std::endl;
    sleep(5);
    return 0;
}

运行结果:
在这里插入图片描述
我们看到线程ID值很大,tid的本质是一个地址
为什么tid不用Linux中的LWP呢?
因为目前用的不是Linux自带的创建线程的接口,我们用的是pthread库中的接口。
我们知道线程共享进程的地址空间
在这里插入图片描述
但是线程有自己独立的栈结构,那么如何保证栈区是每一个线程独占的呢?---->原本的栈给main线程使用,而其余线程把共享区当做栈区。所以每个线程的tid就是自己栈区的起始地址
在这里插入图片描述
见一见
在这里插入图片描述
pthread库时通过clone做到上面的那点。
在这里插入图片描述
那么我们如何获取线程的id呢?
在这里插入图片描述
代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <cstdio>

void *threadRoutine(void *arg)
{
    const std::string name = (char *)arg;
    while (true)
    {
        std::cout << name << "runing..... id: " << pthread_self() << std::endl;
        sleep(1);

    }

    std::cout << "new thread quit....." << std::endl;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");

    int count = 3;
    while (true)
    {
        std::cout << "main thread id:" << pthread_self() << std::endl;
        if(count++ > 5) break;
        sleep(2);
    }

    void *ret = nullptr;
    pthread_join(tid, &ret);

    std::cout << "ret: " << (long long)ret << std::endl;
    std::cout << "main thread wait done .... main quit!" << std::endl;
    sleep(5);

    return 0;
}

运行代码:
在这里插入图片描述
我们看到我们获取到了不同的线程id

大部分线程的代码是共享的!
一个小实验:
代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <cstdio>

int g_val = 0;
void *threadRoutine(void *arg)
{
    const std::string name = (char *)arg;

    while(true)
    {
        std::cout << name << " g_val: " << g_val << " &g_val" << &g_val << std::endl;
        g_val++;
        sleep(1);
    }

}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");

    while(true)
    {
        std::cout << "main thread g_val: " << g_val << " &g_val" << &g_val << std::endl;
        sleep(1);
    }

    return 0;
}

运行结果:
在这里插入图片描述
我们看到g_val被大家所共享,大家都可以看到g_val,一个线程对其进程改变,其它线程都看的到。
那么如果线程想要自己是私有的变量呢?该如何?
只要在变量前加__thread即可。
代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <cstdio>

__thread int g_val = 0;
void *threadRoutine(void *arg)
{

    while(true)
    {
        std::cout << (char*)arg << ": "<< g_val << " &: " << &g_val << std::endl;
        g_val++;
        sleep(1);
    }
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");

    while(true)
    {
        std::cout << "main thread: " << g_val << " &: " << &g_val << std::endl;
        sleep(1);
    }

    return 0;
}

运行代码:
在这里插入图片描述
这里运行的时候是并行执行的所以会看不清,但是我们也能看到,两个变量的地址不一样的。

__thread:修饰全局变量,带来的结果就是让每一个线程各自拥有一个全局变量---->线程的就不存储。

我们之前学过进程替换,如果线程进行进程替换会如何?
代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <cstdio>

__thread int g_val = 0;
void *threadRoutine(void *arg)
{
    execl("/bin/ls","ls",nullptr);
    while(true)
    {
        std::cout << (char*)arg << ": "<< g_val << " &: " << &g_val << std::endl;
        g_val++;
        sleep(1);
    }

}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");

    while(true)
    {
        std::cout << "main thread: " << g_val << " &: " << &g_val << std::endl;
        sleep(1);
    }
    return 0;
}

运行结果:
在这里插入图片描述
我们看到ls确实被执行了,但是整个进程的代码都被替换掉了。

分离线程
默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏
如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。

测试代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <cstdio>
#include <cerrno>
#include <cstring>

__thread int g_val = 0;
void *threadRoutine(void *arg)
{
    pthread_detach(pthread_self());
    while(true)
    {
        std::cout << (char*)arg << ": "<< g_val << " &: " << &g_val << std::endl;
        g_val++;
        sleep(1);
    }
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");

    while(true)
    {
        std::cout << "main thread: " << g_val << " &: " << &g_val << std::endl;
        sleep(1);
        break;
    }

    int n = pthread_join(tid,nullptr);
    std::cout << "n:" << n << " errstring: " << strerror(n) << std::endl;
    return 0;
}

运行结果:
在这里插入图片描述
我们看到join异常进程直接退出。

所以线程分离后线程异常也会影响整个进程

C++语言提供的线程,而语言级别的线程库必须调用原生线程库---->本质是对原生线程库的封装
代码:
在这里插入图片描述
运行:
在这里插入图片描述
进程线程间的互斥相关背景概念
临界资源:多线程执行流共享的资源就叫做临界资源
临界区:每个线程内部,访问临界自娱的代码,就叫做临界区
互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
原子性(后面讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

如果多个线程访问同一个全局变量,并对它进行数据计算,多线程会互相影响吗?
测试代码:
抢票代码

#include <iostream>
#include <thread>
#include <pthread.h>
#include <unistd.h>
#include <cstdio>
#include <cerrno>
#include <cstring>

int tickets = 10000;
void *GetTickets(void *args)
{
    while (true)
    {
        if (tickets > 0)
        {
            usleep(1000);
            printf("%p : %d\n", pthread_self(), tickets);
            tickets--;
        }
        else
        {
            break;
        }
    }

    return nullptr;
}

int main()
{
    pthread_t t1;
    pthread_t t2;
    pthread_t t3;

    pthread_create(&t1, nullptr, GetTickets, nullptr);
    pthread_create(&t2, nullptr, GetTickets, nullptr);
    pthread_create(&t3, nullptr, GetTickets, nullptr);

    pthread_join(t1, nullptr);
    pthread_join(t2, nullptr);
    pthread_join(t3, nullptr);

    return 0;
}

运行结果:
在这里插入图片描述
我们发现票抢到-1了,这肯定是错的!
每次运行的结果都不一定一样:
在这里插入图片描述
所以tickets在并发访问的时候,导致了我们数据不一致的问题。之后再解决这个歌问题。

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

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

相关文章

【Java】零基础蓝桥杯算法学习——二分查找

算法模板一: // 数组arr的区间[0,left-1]满足arr[i]<k,[left,n-1]满足arr[i]>k;Scanner scan new Scanner(System.in);int[] arr {1,2,3,4,5};int left 0,right arr.length-1;int k scan.nextInt();while(left<right) {//leftright时退出循环int mid (leftrigh…

【Linux 02】权限基本概念

文章目录 &#x1f308; Ⅰ 权限概念&#x1f308; Ⅱ 权限管理1. 文件访问者分类 (角色)2. 文件类型和访问权限 (事物属性)3. 文件权限值表示方法 &#x1f308; Ⅲ 权限修改1. chmod 设置文件访问权限2. chown 修改文件拥有者3. chgrp 修改文件或目录的所属组 &#x1f308; …

【ArcGIS Pro二次开发】(79):符号系统_CIMUniqueValueRenderer

CIMUniqueValueRenderer是ArcGIS Pro SDK中的一个类&#xff0c;用于创建唯一值渲染器&#xff08;Unique Value Renderer&#xff09;。 在ArcGIS Pro中长这样&#xff1a; 通过对CIMUniqueValueRenderer的操作&#xff0c;可以对符号系统进行更改&#xff0c;实现很多功能。…

Matplotlib Figure与Axes速成:核心技能一网打尽

Matplotlib Figure与Axes速成&#xff1a;核心技能一网打尽 &#x1f335;文章目录&#x1f335; &#x1f333;引言&#x1f333;&#x1f333; 一、Figure&#xff08;图形&#xff09;&#x1f333;&#x1f341;1. 创建Figure&#x1f341;&#x1f341;2. 添加Axes&#…

【教学类-47-01】20240206UIBOT+IDM下载儿童古诗+修改文件名

背景需求&#xff1a; 去年12月&#xff0c;我去了其他幼儿园参观&#xff0c;这是一个传统文化德育教育特色的学校&#xff0c;在“古典集市”展示活动中&#xff0c;小班中班大班孩子共同现场念诵《元日》《静夜思》包含了演唱版本和儿歌念诵版本。 我马上也要当班主任了&a…

MyBatis中的XML实现和动态SQL实现

文章目录 一、XML实现1.1增1.2删1.3查1.4改 二、XML方式实现动态SQL2.1if标签2.2trim标签2.3where标签2.4set标签2.5foreach标签2.6include标签和sql标签 一、XML实现 先在新建的XML文件中写入如下内容&#xff1a; <?xml version"1.0" encoding"UTF-8&qu…

php基础学习之分支结构和循环结构(不细讲,来对比一下和两大常用高级编程语言(C++/Java)的细微区别以便记忆)

分支结构 常见分支结构 编程语言常见分支结构有&#xff1a; if语句if-else语句if-elseif-else语句switch语句 其中&#xff0c;除了if-elseif-else语句外&#xff0c;另外3中分支语句在php中和C/Java是一模一样的&#xff01; 而if-elseif-else的唯一不同点就在&#xff0c;【…

【Python网络编程之DHCP服务器】

&#x1f680; 作者 &#xff1a;“码上有前” &#x1f680; 文章简介 &#xff1a;Python开发技术 &#x1f680; 欢迎小伙伴们 点赞&#x1f44d;、收藏⭐、留言&#x1f4ac; Python网络编程之DHCP服务器 代码见资源&#xff0c;效果图如下一、实验要求二、协议原理2.1 D…

奇异递归模板模式应用-对象计数

需求&#xff1a;有时遇到某些类特征相似而又没有共同的父类&#xff0c;希望能够知道这些类的创建数量之和。 思路&#xff1a;将这些类继承自同一个计数类&#xff0c;共享计数变量s_createCount信息&#xff0c;实现如下&#xff1a; class Counter { public:Counter() {s_…

Linux操作系统基础(十五):Shell编程

文章目录 Shell编程 一、什么是Shell 1、简介 2、Shell解释器 二、快速入门 1、编写Shell脚本 2、执行Shell脚本 Shell编程 一、什么是Shell 1、简介 Shell 是一个用 C 语言编写的程序&#xff0c; 通过 Shell 用户可以访问操作系统内核服务。 它类似于 DOS 下的 co…

【实战】一、Jest 前端自动化测试框架基础入门(二) —— 前端要学的测试课 从Jest入门到TDD BDD双实战(二)

文章目录 一、Jest 前端自动化测试框架基础入门5.Jest 中的匹配器toBe 匹配器toEqual匹配器toBeNull匹配器toBeUndefined匹配器和toBeDefined匹配器toBeTruthy匹配器toBeFalsy匹配器数字相关的匹配器字符串相关的匹配器数组相关的匹配器异常情况的匹配器 6.Jest 命令行工具的使…

【python】object() takes no parameters报错原因

__init__ 你以为写对了&#xff0c;其实错了&#xff0c;因为是左右都是2个下划线。是左边两个&#xff01;&#xff01;右边也是两个&#xff01;&#xff01;&#xff01;不是合计2个&#xff01;&#xff01;&#xff01;

30. 异常

异常 1. 概述2. Throwable 方法2.1 概述2.2 代码示例 3. 异常分类4. 异常处理方式4.1 JVM默认处理异常4.2 自己处理&#xff08;捕获异常&#xff09;try...catch4.2.1 概述4.2.2 灵魂四问 4.3 抛出处理(throw和throws) 5. 自定义异常5.1 概述5.2 代码示例 6. 异常注意事项 文章…

GPT-4影响高度创新思维的领域(一)

GPT-4的应用范围不再局限于对现有信息的检索、整理和复述&#xff0c;而是进一步拓展到了诸如文学创作、科学假设生成、教育辅导、商业策略建议等需要高度创新思维的领域。这种独立思考和创新能力赋予了GPT-4作为虚拟助手时更加丰富多元的角色定位&#xff0c;使其成为一种强大…

如何在PDF 文件中删除页面?

查看不同的工具以及解释如何在 Windows、Android、macOS 和 iOS 上从 PDF 删除页面的步骤&#xff1a; PDF 是最难处理的文件格式之一。曾经有一段时间&#xff0c;除了阅读之外&#xff0c;无法用 PDF 做任何事情。但是今天&#xff0c;有许多应用程序和工具可以让您用它们做…

寒假思维训练day21

今天更新一道不错的状态压缩DP题&#xff0c;顺带总结一下状态压缩DP。 摘要&#xff1a; Part1 浅谈状态压缩DP的理解 Part2 浅谈对状态机DP的理解 Part3 关于状态压缩DP的1道例题 Part1 状态压缩DP 1、状态压缩DP&#xff1a; 事物的状态可能包含多个特征&#xff0c;…

Zotero插件分享(第二弹)

今天紧接上一篇文章&#xff08;Zotero常用插件分享&#xff09;&#xff0c;继续分享关于Zotero常用插件的相关内容。&#xff08;排名不分先后&#xff09; 1.Translate for Zotero 英文文献阅读辅助工具&#xff0c;可以实现将pdf中选中的文字翻译为指定语言&#xff0c;并…

【学网攻】 第(27)节 -- HSRP(热备份路由器协议)

系列文章目录 目录 系列文章目录 文章目录 前言 一、HSRP(热备份路由器协议)是什么&#xff1f; 二、实验 1.引入 实验目标 实验背景 技术原理 实验步骤 实验设备 实验拓扑图 实验配置 实验验证 文章目录 【学网攻】 第(1)节 -- 认识网络【学网攻】 第(2)节 -- 交…

港口码头航吊远距离相位测距仪|传感器PHR-120100配置使用说明

港口码头航吊远距离相位测距仪|传感器PHR-120100广泛应用于港口码头、航车、位移监测、形变监测、钢铁、堆垛机、龙门吊等领域。 本文重点介绍港口码头航吊远距离相位测距仪|传感器PHR-120100配置使用说明。 一、布局介绍 二、按键介绍 三、指示灯说明 四、显示屏操作说明 …

使用LORA微调RoBERTa

模型微调是指在一个已经训练好的模型的基础上&#xff0c;针对特定任务或者特定数据集进行再次训练以提高性能的过程。微调可以在使其适应特定任务时产生显着的结果。 RoBERTa&#xff08;Robustly optimized BERT approach&#xff09;是由Facebook AI提出的一种基于Transfor…