<Linux线程概念及线程控制>——《Linux》

news2024/11/19 23:33:47

目录

1. Linux线程概念

什么是线程

线程的优点

线程的缺点

线程异常

线程用途

2. Linux进程VS线程

进程和线程

进程的多个线程共享

关于进程线程的问题

3. Linux线程控制

POSIX线程库

创建线程

线程ID及进程地址空间布局

线程终止

线程等待

4. 分离线程

后记:●由于作者水平有限,文章难免存在谬误之处,敬请读者斧正,俚语成篇,恳望指教!

                                                                           ——By 作者:新晓·故知


本节重点:
1. 了解线程概念,理解线程与进程区别与联系。
2. 学会线程控制,线程创建,线程终止,线程等待。
3. 了解线程分离与线程安全概念。
4. 学会线程同步。
5. 学会使用互斥量,条件变量, posix 信号量,以及读写锁。
6. 理解基于读写锁的读者写者问题。

1. Linux线程概念

什么是线程

  • 在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是一个进程内部的控制序列”
  • 一切进程至少都有一个执行线程
  • 线程在进程内部运行,本质是在进程地址空间内运行
  • Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化
  • 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流。


线程:
(1)在进程内部运行的执行流
(2)线程比进程粒度更细,调度成本更低
(3)线程是CPU调度的基本单位
我们在之前学习过,fork()之后,父、子进程是共享代码的。可以通过if else 进行判断让父子进程执行不同的代码块。不同的执行流,可以做到进行对特定资源的划分。
线程:进程   ——  N:1 
线程也有描述的数据结构(TCB),在Linux中,进程和线程在概念层面没有区分,只有一个称为执行流。

线程的优点

  • 创建一个新线程的代价要比创建一个新进程小得多
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
  • 线程占用的资源要比进程少很多
  • 能充分利用多处理器的可并行数量
  • 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现 I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

线程的缺点

性能损失
一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额 外的同步和调度开销,而可用的资源不变。
健壮性降低
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
缺乏访问控制
进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
编程难度提高
编写与调试一个多线程程序比单线程程序困难得多

线程异常

  • 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
  • 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出

线程用途

  • 合理的使用多线程,能提高CPU密集型程序的执行效率
  • 合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现)

2. Linux进程VS线程

进程和线程

  • 进程是资源分配的基本单位
  • 线程是调度的基本单位
  • 线程共享进程数据,但也拥有自己的一部分数据:  

    线程ID

    一组寄存器
    errno
    信号屏蔽字
    调度优先级

进程的多个线程共享

同一地址空间 , 因此 Text Segment Data Segment 都是共享的 , 如果定义一个函数 , 在各线程中都可以调用 , 如果定义一个全局变量, 在各线程中都可以访问到 , 除此之外 , 各线程还共享以下进程资源和环境 :
  • 文件描述符表
  • 每种信号的处理方式(SIG_ IGNSIG_ DFL或者自定义的信号处理函数)
  • 当前工作目录
  • 用户id和组id
进程和线程的关系如下图 :

关于进程线程的问题

如何看待之前学习的单进程?具有一个线程执行流的进程

我们之前理解进程:进程=内核数据结构+进程对应的代码和数据

现在理解进程:从内核视角,进程是承担分配系统资源的基本实体,是向系统申请资源的基本单位!

内部只有一个执行流的进程——单执行流进程

背部有多个执行流的进程——多执行流进程

 多个线程谁先运行,由OS决定。

#include <iostream>
#include <string>
#include <unistd.h>
#include <pthread.h>
using namespace std;

void *callback1(void *args)
{
    string name = (char *)args;
    while (true)
    {
        cout << name << ": " << ::getpid() << endl;
        sleep(1);
    }
}
void *callback2(void *args)
{
    string name = (char *)args;
    while (true)
    {
        cout << name << ": " << ::getpid() << endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid1;
    pthread_t tid2;

    pthread_create(&tid1, nullptr, callback1, (void *)"thread 1");
    pthread_create(&tid2, nullptr, callback2, (void *)"thread 2");

    while (true)
    {
        cout << "我是主线程... :" << ::getpid() << endl;
        sleep(1);
    }

    pthread_join(tid1, nullptr);
    pthread_join(tid2, nullptr);

    return 0;
}

 C++也支持线程,其封装的是系统原生的pthread。

Linux下的线程是进程模拟的,是轻量级进程。

 

 

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;

void *startRoutine(void *args)
{
    while (true)
    {
        cout << "线程正在运行..." << endl;
        sleep(1);
    }
}
int main()
{
    pthread_t tid;
    int n = pthread_create(&tid, nullptr, startRoutine, (void *)"thread 1");
    cout << "new thread id: " << tid << endl;   //线程id
    while (true)
    {
        cout << " main thread 正在运行..." << endl;
        sleep(1);
    }
    return 0;
}

#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <pthread.h>
using namespace std;

static void  printTid(const char* name,const pthread_t &tid)
{
    printf("%s 正在运行,thread id: 0x%x\n",name,tid);
}
void *startRoutine(void *args)
{
    const char* name = static_cast<const char *>(args);
    while (true)
    {
        //cout << "线程正在运行..." << endl;
        printTid(name,pthread_self());
        sleep(1);
    }
}
int main()
{
    pthread_t tid;
    int n = pthread_create(&tid, nullptr, startRoutine, (void *)"thread 1");
    //cout << "new thread id: " << tid << endl;   //线程id
    while (true)
    {
        //cout << " main thread 正在运行..." << endl;
        printTid("main thread" ,pthread_self());
        sleep(1);
    }
    return 0;
}

 

#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <pthread.h>
using namespace std;

static void printTid(const char *name, const pthread_t &tid)
{
    printf("%s 正在运行,thread id: 0x%x\n", name, tid);
}
void *startRoutine(void *args)
{
    const char *name = static_cast<const char *>(args);
    int cnt = 5;
    while (true)
    {
        printTid(name, pthread_self());
        sleep(1);
        if (!(cnt--))
            break;
    }
    cout << "线程退出!" << endl;
    return nullptr;
}
int main()
{
    pthread_t tid;
    int n = pthread_create(&tid, nullptr, startRoutine, (void *)"thread 1");
    sleep(5);
    pthread_join(tid, nullptr);
    cout<<"main thread join success!"<<endl;
    sleep(5);
    while (true)
    {
        printTid("main thread" ,pthread_self());
        sleep(1);
    }
    return 0;
}

 shell脚本:

while :; do ps -aL | head -1 && ps -aL |  grep mythread; sleep 1;done
while :; do ps ajx | grep mythread; sleep 1; done

线程退出的时候,一般需要进行join,否则就会造成类似于进程那样的内存泄漏问题!

#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <pthread.h>
using namespace std;

static void printTid(const char *name, const pthread_t &tid)
{
    printf("%s 正在运行,thread id: 0x%x\n", name, tid);
}
void *startRoutine(void *args)
{
    const char *name = static_cast<const char *>(args);
    int cnt = 5;
    while (true)
    {
        printTid(name, pthread_self());
        sleep(1);
        if (!(cnt--))
            break;
    }
    cout << "线程退出!" << endl;
    // 线程退出方式:
    // 1.return
    return (void *)10;
}
int main()
{
    pthread_t tid;
    int n = pthread_create(&tid, nullptr, startRoutine, (void *)"thread 1");
    (void)n;
    // sleep(1);
    void *ret = nullptr; // void **retval是一个输出型参数
    pthread_join(tid, &ret);
    cout << "main thread join success!  *ret: " << (long long)ret << endl;
    sleep(1);
    while (true)
    {
        printTid("main thread", pthread_self());
        sleep(1);
    }
    return 0;
}

 

-1 其实是内核中“取消”定义的宏。 

pthread_t其实是一个地址。

 

 

 

 

 主线程(进程)退出,则所有线程退出!

1. 立即分离,延后分离 -- 线程活着 -- 意味着,我们不在关心这个线程的死活。( 线程退出的第四种方式,延后退出)

2. 新线程分离,但是主线程先退出(进程退出) --- 一般我们分离线程,对应的main thread一般不要退出(常驻内存的进程)

3. Linux线程控制

POSIX线程库

  • 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
  • 要使用这些函数库,要通过引入头文<pthread.h>
  • 链接这些线程函数库时要使用编译器命令的“-lpthread”选项

创建线程

功能:创建一个新的线程
原型
 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(*start_routine)(void*), void *arg);
参数
 thread:返回线程ID
 attr:设置线程的属性,attr为NULL表示使用默认属性
 start_routine:是个函数地址,线程启动后要执行的函数
 arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码
错误检查 :
  • 传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。
  • pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误代码通过返回值返回
  • pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值业判定,因为读取返回值要比读取线程内的errno变量的开销更小
    #include <unistd.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <pthread.h>
    void *rout(void *arg) {
        int i;
        for( ; ; ) {
            printf("I'am thread 1\n");
            sleep(1);
       } 
    }
    int main( void )
    {
        pthread_t tid;
        int ret;
        if ( (ret=pthread_create(&tid, NULL, rout, NULL)) != 0 ) {
            fprintf(stderr, "pthread_create : %s\n", strerror(ret));
            exit(EXIT_FAILURE);
       }
        int i;
        for(; ; ) {
            printf("I'am main thread\n");
            sleep(1);
       }
    }

线程ID及进程地址空间布局

  • pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。
  • 前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
  • pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
  • 线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID
pthread_t pthread_self(void);
pthread_t 到底是什么类型呢?取决于实现。对于 Linux 目前实现的 NPTL 实现而言, pthread_t 类型的线程 ID ,本质就是一个进程地址空间上的一个地址。

 

线程终止

如果需要只终止某个线程而不终止整个进程 , 可以有三种方法 :
1. 从线程函数 return 。这种方法对主线程不适用 , main 函数 return 相当于调用 exit
2. 线程可以调用 pthread_ exit 终止自己。
3. 一个线程可以调用 pthread_ cancel 终止同一进程中的另一个线程。
pthread_exit函数
功能:线程终止
原型
 void pthread_exit(void *value_ptr);
参数
 value_ptr:value_ptr不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
需要注意 ,pthread_exit 或者 return 返回的指针所指向的内存单元必须是全局的或者是用 malloc 分配的 , 不能在线程函数的栈上分配, 因为当其它线程得到这个返回指针时线程函数已经退出了。
pthread_cancel函数
功能:取消一个执行中的线程
原型
int pthread_cancel(pthread_t thread);
参数
thread:线程ID
返回值:成功返回0;失败返回错误码

线程等待

为什么需要线程等待?
  • 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
  • 创建新的线程不会复用刚才退出线程的地址空间。
功能:等待线程结束
原型
int pthread_join(pthread_t thread, void **value_ptr);
参数
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码

调用该函数的线程将挂起等待 , 直到 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参数。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *thread1( void *arg )
{
    printf("thread 1 returning ... \n");
    int *p = (int*)malloc(sizeof(int));
    *p = 1;
    return (void*)p;
    }
void *thread2( void *arg )
{
    printf("thread 2 exiting ...\n");
    int *p = (int*)malloc(sizeof(int));
    *p = 2;
    pthread_exit((void*)p);
}
void *thread3( void *arg )
{
    while ( 1 ){ // 
        printf("thread 3 is running ...\n");
        sleep(1);
   }
    return NULL;
}
int main( void )
{
    pthread_t tid;
    void *ret;
    // thread 1 return
    pthread_create(&tid, NULL, thread1, NULL);
    pthread_join(tid, &ret);
    printf("thread return, thread id %X, return code:%d\n", tid, *(int*)ret);
    free(ret);
    // thread 2 exit
    pthread_create(&tid, NULL, thread2, NULL);
    pthread_join(tid, &ret);
    printf("thread return, thread id %X, return code:%d\n", tid, *(int*)ret);
    free(ret);
    
    // thread 3 cancel by other
    pthread_create(&tid, NULL, thread3, NULL);
    sleep(3);
    pthread_cancel(tid);
    pthread_join(tid, &ret);
    if ( ret ==  PTHREAD_CANCELED )
        printf("thread return, thread id %X, return code:PTHREAD_CANCELED\n", tid);
    else
        printf("thread return, thread id %X, return code:NULL\n", tid);
}
运行结果:
 [root@localhost linux]# ./a.out
 thread 1 returning ... 
 thread return, thread id 5AA79700, return code:1
 thread 2 exiting ...
 thread return, thread id 5AA79700, return code:2
 thread 3 is running ...
 thread 3 is running ...
 thread 3 is running ...
 thread return, thread id 5AA79700, return code:PTHREAD_CANCELED

4. 分离线程

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

joinable和分离是冲突的,一个线程不能既是joinable又是分离的

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *thread_run( void * arg )
{
   pthread_detach(pthread_self());
   printf("%s\n", (char*)arg);
   return NULL;
}
int main( void )
{
   pthread_t tid;
   if ( pthread_create(&tid, NULL, thread_run, "thread1 run...") != 0 ) {
       printf("create thread error\n");
       return 1;
   }
   int ret = 0;
   sleep(1);//很重要,要让线程先分离,再等待
   if ( pthread_join(tid, NULL ) == 0 ) {
       printf("pthread wait success\n");
       ret = 0;
   } else {
       printf("pthread wait failed\n");
       ret = 1;
 }
   return ret;
}

后记:
●由于作者水平有限,文章难免存在谬误之处,敬请读者斧正,俚语成篇,恳望指教!

                                                                           ——By 作者:新晓·故知

 

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

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

相关文章

ArcGIS 制作这种“清新设计风”的地图海报封面

这种图怎么做?下面是星球研究所制作的一张地图海报,那么究竟是如何制作的呢? 星球研究所 制作 一通鼓捣之下,使用 ArcGIS Pro 制作出了风格相近的成都市区位及地形示意图地图(其实 QGIS 也能做)。 下面教程一并分享给大家。 完全使用 ArcGIS Pro 制作的成果图 1.全国区位…

Python——几个常用的数学函数

1. min()函数&#xff1a;取出给定参数的最小值 说明&#xff1a;获取指定数值或者指定序列中最小值。 print(min(1, 5)) print(min(1, 2, 3, 4, 5, 6)) print(min([2, 3, 4, 5])) 2.max()函数&#xff1a;取出给定参数的最大值 说明&#xff1a;获取指定数值或者指定序列中…

CSS初级教程【第一天】

CSS初级教程【第一天】【1】CSS初识【2】CSS简介【3】CSS语法【4】CSS 选择器【5】CSS使用【6】CSS 注释【7】CSS 颜色【8】CSS RGB 颜色【9】CSS HEX 颜色【10】CSS HSL 颜色&#xff08;色相| 饱和度 | 明度&#xff09;【0】末尾声名【1】CSS初识 CSS 是一种描述 HTML 文档样…

【青岛大学·王卓】第3章_栈和队列

【青岛大学王卓】第3章_栈和队列 20221107-20221119 3.1 栈和队列的定义和特点 普通线性表插入和删除可以是线性表中的任意为位置&#xff1b; 3.1.1 栈 栈的概念 栈和队列是两种常用的、重要的数据结构。栈和队列是限定插入和删除只能在表的端点进行的线性表。 栈特点 后…

Anki学习之路

【常规操作】&#xff1a; 【自定义卡片进度】&#xff1a; [右键单击] -> [重设学习进度] //选择放到哪一个队列中&#xff08; 新卡片队列 / 复习队列 &#xff09;。 【重新学一遍】&#xff1a; //然后再进行上面的操作步骤。 【牌组齿轮按钮】&#xff1a; 【每日新…

Web前端105天-day62-HTML5_CORE

HTML5CORE02 目录 前言 一、复习 二、拖拽 三、上传服务器 四、Canvas 五、地图 总结 前言 HTML5CORE02学习开始 一、复习 跨域 浏览器的同源策略导致在网页中, 通过 AJAX 发送网络请求时, 默认只能向同源的服务器请求同源: 协议 端口号 域名 三者都相同产生跨域的原因…

杰华特科创板上市:市值227亿 华为英特尔联想是股东

雷递网 雷建平 12月23日杰华特微电子股份有限公司&#xff08;简称&#xff1a;“杰华特”&#xff0c;股票代码为&#xff1a;“688141” &#xff09;今日在科创板上市&#xff0c;发行价为38.26元。杰华特此次发行5808万股&#xff0c;发行价为38.26元&#xff0c;募资总额为…

32天高效突击:开源框架+性能优化+微服务架构+分布式,面阿里获P7(脑图、笔记、面试考点全都有)

今年的大环境不佳&#xff0c;所以大部分的人在今年的招聘旺季都没有收获到好的结果。 但不要着急&#xff0c;今天分享的内容则是由 一位阿里P7的面试心得&#xff0c;通过32天的高效突击训练&#xff0c;成功拿下offer的学习方法。 篇章分为三大章节&#xff0c;可以根据自…

【TypeScript】类型兼容性与交叉类型讲解

目录 类型兼容性 对象类型兼容性 接口类型兼容性 函数类型兼容性 交叉类型 类型兼容性 在TS中&#xff0c;类型采用的是结构化类型系统&#xff0c;也叫做 duck typing&#xff08;鸭子类型&#xff09;&#xff0c;类型检查关注的是值所具有的形状。也就是说&#xff0c…

C. Building a Fence(范围判定)

Problem - 1469C - Codeforces 你想建造一个由n个相等部分组成的栅栏。所有部分的宽度都等于1&#xff0c;高度都等于k。 不幸的是&#xff0c;篱笆下面的地面并不平坦。为了简单起见&#xff0c;你可以认为第i节下面的地面等于hi。 你应该遵循几个规则来建造围栏。 连续的部…

C++知识总结

1.C面向对象三大特征&#xff1a;继承、封装、多态。其中多态分为静态多态和动态多态。静态多态&#xff1a;重载&#xff0c;参数模板 动态多态&#xff1a;虚函数&#xff0c;强制转换。 2.static类型的变量存在静态存储区&#xff0c;初始值为0或者null 3.char *p“PCGAME”…

kubekey初期尝试安装 KubeSphere单机和多机避坑指南

kubekey初期尝试安装 KubeSphere单机和多机避坑指南 准备工作 请注意开始前工作确定各个软件版本情况&#xff0c;本文章要想阅读比较舒服请还得有些Go开发经验 CentOS 7.9 KubeKey v1.21 KubeSphere v3.2.1 Docker 和 Kubernetes 根据支持进行选择&#xff1a; 获取支持可以通…

【复盘】2022年度复盘

年度总结 今年的年度总结比之前写早了一点&#xff0c;主要在因为居家办公时间太久&#xff0c;正好有空就找点时间提前写一下总结复盘计划&#xff0c;说实话要是每月都写一次&#xff0c;我自己也做不到。今年这一年如果用两个字来形容的话&#xff0c;应该是坚定 工作篇 …

用树莓派4B安装gitlab,亲测可用~

最近成功在CentOS7上安装了gitlab&#xff0c;忽然想到是不是可以把吃灰的树莓派4B也装上gitlab&#xff0c;于是研究了一下&#xff0c;做个分享。 树莓派是4B 8G版本。本身装的是官方的64位系统。之前可能还装过一些乱七八糟的东西&#xff0c;这里就不提了。 上gitlab官网…

m基于GRNN广义回归神经网络的飞机发动机剩余寿命预测matlab仿真,训练集采用C-MAPSS数据集

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 GRNN建立在非参数核回归基础上&#xff0c;以样本数据为后验条件&#xff0c;通过执行诸如Parzen非参数估计&#xff0c;从观测样本里求得自变量和因变量之间的联结概率密度函数之后&#xff0c;…

mysql查询某字符串是否在某字段中精确查找是否存在和case when语法规则使用说明

mysql中函数find_in_set可以对字符串在某个字段中是否存在&#xff0c;如果存在则返回含有该查询项的所有记录 SELECT count(1) FROM contingency_plan_team_role WHERE find_in_set( 36, preview_task_ids); 查询结果&#xff1a; case when 进行分组判断 cas…

【数据结构与算法基础】青岛大学王卓老师

【数据结构与算法基础】 1. 学习笔记参考 《数据结构与算法基础》教学视频目录87师兄-B站课程《数据结构与算法基础》脑图 2. 学习章节 【青岛大学王卓】第1章_前言【青岛大学王卓】第2章_线性表【青岛大学王卓】第3章_栈和队列【青岛大学王卓】第4章_串、数组和广义表【青岛…

Python爬虫入门——BeautifulSoup库

一、前言 这篇来演示如何使用BeautifulSoup模块来从HTML文本中提取我们想要的数据。 update on 2016-12-28&#xff1a;之前忘记给BeautifulSoup的官网了&#xff0c;今天补上&#xff0c;顺便再补点BeautifulSoup的用法。 update on 2017-08-16&#xff1a;很多网友留言说U…

搜索(5):迭代加深、双向dfs

活动 - AcWing 算法竞赛进阶指南 一、迭代加深概述 dfs每次选定一个分支&#xff0c;直到抵达递归边界才回溯&#xff0c;这种策略有一定缺陷。当搜索树的某个分支情况非常多&#xff0c;并且问题的答案在一个较浅的分支上时&#xff0c;如果一开始就选错了分支&#xff0…

在宜宾,看见未来中国的产融平台样本

在被验证的京东西南数字化产融协同平台背后&#xff0c;恰证明着在京东这样的新型实体企业支持下&#xff0c;中国的区域产业经济已经出现星星之火&#xff0c;而这些星星之火正在帮助成千上万的企业走出固有的销售渠道和销售模型&#xff0c;成为新时代数字经济和产业经济的一…