多线程(pthread库)

news2024/11/24 16:35:56

POSIX线程库

引言

前面我们提到了Linux中并无真正意义上的线程
从OS角度来看,这意味着它并不会提供直接创建线程的系统调用,它最多给我们提供创建轻量级进程LWP的接口
但是从用户的角度来看,用户只认识线程啊!
因此,操作系统OS与用户两者之间,必定存在一个桥梁——库
这个线程库,对下能够将Linux提供的LWP进程接口进行封装,对上能够给用户进行线程控制的接口
这个库,我们就称作pthread库,在里面的绝大多数函数的名字都是以“pthread_”打头
在任何linux系统下,不管版本的老旧,都会默认自带,是一个原生线程库,等下我们也会进行验证

前提

但是,pthread线程库,并非随意就能使用,还需要我们编写代码时,进行一些附加操作

1.要使用这些函数库,要引入头文件<pthread.h>
2.链接这些线程函数库时要使用编译器命令的“-lpthread”选项

  1 mythreadTest:threadTest.cc
  2   g++ -o $@ $^ -std=c++11 -lpthread                                                                                                                                 
  3 .PHONY:clean
  4 clean:
  5   rm -f mythreadTest

pthread_create函数

就像每个文件都有着其对应的inode编号,每个线程也有着自己的编号,它的类型时pthread_t类型
假如在编译器中一直跳转,寻找它最开始的定义
在这里插入图片描述
可以发现,它实际上就是一个unsigned long类型
不过具体这个编号有什么用,我们先按下不表
我们先介绍创建线程提供的pthread_create函数
man手册查该函数,会给出相应的函数介绍
可以看到该函数位于3号手册中,所以也符合我们前面的讲解,即该函数不是系统调用的函数,而是封装了linux系统的轻量级进程接口的函数
功能是创建一个新线程(create a new thread)
在这里插入图片描述
总共有4个参数

第一个参数thread
是线程id的地址(返回线程ID)
第二个参数attr,
设置线程的属性,attr为NULL表示使用默认属性,通常使用的时候都给nullptr,使用默认属性
第三个参数start_routine
是个函数地址,线程启动后要执行的函数,参数是void*,返回参数也是void*,是一个函数指针
第四个参数arg
是等下传给start_routine的参数(传给线程启动函数的参数)

有了上面的基础后,我们就可以先简单创建一个我们的线程
主线程输出对应的线程id
另外一个线程输出自己正在允许

    1 #include <iostream>
    2 #include <unistd.h>
    3 
    4 using namespace std;
    5 void* thread_run(void* args)
    6 {
    7   while(true)
    8   {
    9     cout << "new thread is running" << endl;
   10     sleep(1);                                                                                                                                                     
   11   }
   12 }
   13 
   14 int main()
   15 {
   16   pthread_t t;
   17   pthread_create(&t,nullptr,thread_run,nullptr);
   18 
   19   while(true)
   20   {
   21     cout << "main thread is running,thread id : " << t << endl;
   22     sleep(1);
   23   }
   24   return 0;
   25 }

可以看到,结果符合我们的预期
在这里插入图片描述
往命令行窗口输入lld + 对应文件名,即可看到该文件链接了什么库
可以看到,其中有一个pthread库,它对应的路径是/lib64/libpthread,也就和我们之前所说的任何Linux系统默认自带相应的pthread库说法,完美符合
在这里插入图片描述
但是我们现在只是创建了一个线程而已,所以,我们的代码肯定还是要改进,创建更多的线程的
我们采取数组的方式,我们知道数组名实际上就是首元素的地址,加上对应的i,实际对应的刚好就是数组里面每个元素的地址,而不用再取地址&
并且,我们可以开始研究第四个参数arg,它是主线程往新线程里面传的参数
那实际上能不能传过去呢?我们对传进来的参数args进行强制类型转换,然后打印相应的内容,假如能够打印相应的内容,则说明args这个参数的确能够是主线程往新线程里面传的参数

  1 #include <iostream>
  2 #include <unistd.h>
  3 #define NUM 10
  4 using namespace std;
  5 void* thread_run(void* args)
  6 { 
  7   char* name = (char*)args;
  8 
  9   while(true)
 10   {
 11     cout << "new thread: " << name << " is running" << endl;
 12     sleep(1);
 13   }
 14 
 15   return nullptr;                                                                                                                                                   
 16 }
 17 
 18 int main()
 19 {
 20   pthread_t tid[NUM];
 21   for (int i = 0;i < NUM;i++)
 22   {
 23      char tname[64];
 24      snprintf(tname,sizeof(tname),"thread-%d",i + 1);
 25      pthread_create(tid + i,nullptr,thread_run,tname);
 26   }
 27 
 28 
 29   while(true)
 30   {
 31     cout << "main thread is running" << endl;
 32     sleep(1);
 33   }
 34   return 0;
 35 }

但是,打印出来的结果,却不符合我们的预期
第一,我们预想的是,每个线程的编号都应该不同,即每个线程的名字都不一样,毕竟我们循环往tname这个数组里面放内容的时候,用的是不同的i
第二,有部分线程输出代码紧挨在一起,并且主线程并不是最先运行的,反而是新线程先运行
在这里插入图片描述
对于第二个问题,我们其实可以解释,在进程的一章中我们就已经提到过,哪个进程先被调度,其实是不确定的,同样的,线程也是我们调用轻量级进程接口创建出来的,肯定也是符合这个规律,所以谁先被调度,完全取决于调度器决定,先创建的线程,不一定被调度
对于第一个问题,就有点难理解
实际上是由于线程共享的是同一份资源,即便这只是一个临时变量
因此,tname里面存的地址,在不同线程看来都是相同的
所以往里面同时写数据,就会覆盖原有tname空间的旧内容
最后剩下的,仅仅是最后调度的线程的名字
那我们要怎么修改呢?
一种简单的方式,就是直接new相应的空间
(不过要注意,此时使用snprintf函数的时候,就不能再直接sizeof,这样计算的就单纯只会是指针的大小,所以这里直接指定64字节,毕竟整个空间也就64字节)
对于每个线程来说,都会new出新的自己的空间,这样放的数据就不会再被覆盖
相当于每个线程,都有了自己的房子,从此井水不犯河水

  1 #include <iostream>
  2 #include <unistd.h>
  3 #define NUM 10
  4 using namespace std;
  5 void* thread_run(void* args)
  6 {
  7   char* name = (char*)args;
  8 
  9   while(true)
 10   {
 11     cout << "new thread: " << name << " is running" << endl;
 12     sleep(1);
 13   }
 14   delete name;                                                                                                                                                      
 15   return nullptr;
 16 }
 17 
 18 int main()
 19 {
 20   pthread_t tid[NUM];
 21   for (int i = 0;i < NUM;i++)
 22   {
 23      char* tp = new char[64];
 24      snprintf(tp,64,"thread-%d",i + 1);
 25      pthread_create(tid + i,nullptr,thread_run,tp);
 26   }
 27 
 28 
 29   while(true)
 30   {
 31     cout << "main thread is running" << endl;
 32     sleep(1);
 33   }
 34   return 0;
 35 }

经过修改后的运行效果,就符合我们的预期了
在这里插入图片描述

线程终止

前面我们提到了在linux系统下,是没有对应具体线程的实现!而是采用复用的方式
所以,进程有的特性,线程往往也会有

主线程提前退出

假如主线程现在提前退出了,说不再和其它新线程一起玩,会发生什么情况呢?
将上面主线程的代码修改一下,把循环去掉
在这里插入图片描述
此时,再编译运行我们的代码,会得到下面的结果
可以看到,一旦主线程退出了,其它的所有新线程,就会全部强制退出
在这里插入图片描述
为什么会出现这种情况呢?
就是因为线程是进程的一个执行分支,线程异常了,发送信号是给进程发信号,进程挂了,所有依附于它的线程,全部都走不了,覆巢之下,安有完卵,指的就是这个道理
同样的,假如其中一个线程调用了exit函数,那请问最后的结果会是怎么样呢?
我们同样可以修改相应的代码,在循环中加入相应的exit函数
在这里插入图片描述
可以看到,只有几个线程成功输出了自己的编号,程序就自动停止了
所以实际的情况就是,有几个线程成功被创建,但是其中有一个线程执行exit函数,然后全部线程都挂掉了
在这里插入图片描述
只要有任何一个线程调用exit函数,整个进程中,所有的线程都会全部退出
关键不是主线程还是新线程的问题,而是大家都是一体的,同生共死

阻塞等待

正是由于线程和进程是有很多相似之处的,进程有父进程阻塞等待,回收子进程的操作
线程也会有相应的概念
主线程需要等待子线程,然后进行相应的回收,否则子线程就会陷入僵尸状态
在pthread库里面就已经提供了相应主线程等待的库函数pthread_join
调用该函数,主线程就会阻塞,并回收相应退出的新线程(join with a terminated thread)
在这里插入图片描述
它总共有两个参数‘
第一个参数thread,就是我们之前提到过的线程id
第二个参数retval,是一个二级指针void**,为什么是二级指针呢?
因为它是一个输出型参数,早在C语言函数中我们就已经学过,由于C语言中没有引用的概念,因此,在函数内部进行赋值,其实改变的都是形参,并不会改变实参
想要改变实参,就需要传相应的指针
想传int出来,就要int*
想传int出来,就要int**作为参数
同理,假如我们返回的参数此时是一个void
类型的,那用void**接收,也就非常合理了
在这里插入图片描述
它的返回值和前面提到过的pthread_create函数相同
成功的话,就返回0;否则,返回一个错误的数字

  1 #include <iostream>
  2 #include <unistd.h>
  3 #define NUM 10
  4 using namespace std;
  5 void* thread_run(void* args)
  6 { 
  7   char* name = (char*)args;
  8 
  9   while(true)
 10   {
 11     cout << "new thread: " << name << " is running" << endl;
 12     sleep(1);
 13   }                                                                                                                                                             
 14   delete name;                                                                                                                                                  
 15   return nullptr;                                                                                                                                               
 16 }                                                                                                                                                               
 17                                                                                                                                                                 
 18 int main()                                                                                                                                                      
 19 {                                                                                                                                                               
 20   pthread_t tid[NUM];                                                                                                                                           
 21   for (int i = 0;i < NUM;i++)                                                                                                                                   
 22   {                                                                                                                                                             
 23      char* tp = new char[64];                                                                                                                                   
 24      snprintf(tp,64,"thread-%d",i + 1);                                                                                                                         
 25      pthread_create(tid + i,nullptr,thread_run,tp);                                                                                                             
 26   }                                                                                                                                                                 
 27 
 28   for (int i = 0;i < NUM;i++)
 29   {
 30     pthread_join(tid[i],nullptr);
 31   }
 32 
 33   return 0;
 34 }

终止方式

既然,我们知道主线程,需要阻塞等待子线程退出,并回收相应的子线程
那了解子线程有多少种退出方式,就非常有必要
子线程总共有两种退出方式
第一种方式,线程函数执行完毕,此时直接返回nullptr,线程就会退出

  1 #include <iostream>
  2 #include <unistd.h>
  3 #define NUM 10
  4 using namespace std;
  5 void* thread_run(void* args)
  6 {                                                                                                                                                                   
  7   char* name = (char*)args;
  8 
  9   while(true)
 10   {
 11     cout << "new thread: " << name << " is running" << endl;
 12     sleep(1);
 13     break;
 14   }
 15   delete name;
 16   return nullptr;
 17 }
 18 int main()
 19 {                                                                                                                                    
 20   pthread_t tid[NUM];                                                                                                                
 21   for (int i = 0;i < NUM;i++)                                                                                                        
 22   {                                                                                                                                  
 23      char* tp = new char[64];                                                                                                        
 24      snprintf(tp,64,"thread-%d",i + 1);                                                                                              
 25      pthread_create(tid + i,nullptr,thread_run,tp);                                                                                  
 26   }                                                                                                                                  
 27                                                                                                                                      
 28   for (int i = 0;i < NUM;i++)                                                                                                        
 29   {                                                                                                                                  
 30     int n = pthread_join(tid[i],nullptr);                                                                                            
 31     //errno变量只有一个,而线程有多个,作同时修改,可能会互相影响                                                                    
 32     if(n!= 0)   cerr << "pthread_join error" << endl;                                                                                
 33   }                                                                                                                                  
 34                                                                                                                                      
 35   return 0; 

但是,还有一种方式,pthread库里面提供了相应的线程退出函数pthread_exit
在这里插入图片描述
它的参数retval,为一个输出型参数
没错,和我们之前提到的pthread_join的参数名字是相同的,也就意味着两者肯定有所关联
通过返回retval,我们对应的主线程就可以接收到对应的错误信息
那为什么我们不通过设置全局变量errno来输出对应的错误信息呢?
因为不同线程对于这个全局变量是共享的,全部线程都同时使用一个全局变量,就可能会出现覆盖等等问题,导致出错了也可能不知道
因此,pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)
而是将错误码通过返回值返回
还有一个好处是,对于pthreads函数的错误,通过返回值判定,要比读取线程内的errno变量的开销更小

  1 #include <iostream>
  2 #include <unistd.h>
  3 #define NUM 10
  4 using namespace std;
  5 void* thread_run(void* args)
  6 { 
  7   char* name = (char*)args;
  8 
  9   while(true)
 10   {
 11     cout << "new thread: " << name << " is running" << endl;
 12     break;
 13   }
 14   delete name;
 15   pthread_exit(nullptr);                                                                                                                                            
 16 }
 17 
 18 
 19 int main()
 20 {
 21   pthread_t tid[NUM];
 22   for (int i = 0;i < NUM;i++)
 23   {
 24      char* tp = new char[64];
 25      snprintf(tp,64,"thread-%d",i + 1);
 26      pthread_create(tid + i,nullptr,thread_run,tp);
 27   }
 28 
 29   for (int i = 0;i < NUM;i++)
 30   {
 31     int n = pthread_join(tid[i],nullptr);
 32     //errno变量只有一个,而线程有多个,作同时修改,可能会互相影响
 33     if(n!= 0)   cerr << "pthread_join error" << endl;
 34   }
 35   cout << "all thread quit" << endl;
 36   return 0;
 37 }

  1 #include <iostream>
  2 #include <unistd.h>
  3 #include <pthread.h>
  4 #include <string>
  5 #include <ctime>
  6 #define NUM 10
  7 using namespace std;
  8 
  9 

 10 class ThreadData
 11 {
 12 public:
 13   ThreadData(const string& name,int id,time_t createTime):_name(name),_id(id),_createTime((uint64_t)createTime)
 14   {}
 15   ~ThreadData()
 16   {}
 17 public:
 18     string _name;
 19     int _id;
 20     uint64_t _createTime;
 21 };

 22 void* thread_run(void* args)
 23 {
 24   ThreadData* tp = static_cast<ThreadData*>(args);
 25 
 26   while(true)
 27   {
 28     cout << "thread is running,name: " << tp->_name << " create time: "<< tp->_createTime << " index:" << tp->_id << endl;                                          
 29     break;
 30   }
 31   delete tp;
 32   pthread_exit((void*)2);
 33 }
 34 
 35 
 36 int main()
 37 {
 38   pthread_t tid[NUM];
 39   for (int i = 0;i < NUM;i++)
 40   {  
 41      char tname[64];
 42      snprintf(tname,sizeof(tname),"thread-%d",i + 1);
 43      ThreadData* tp = new ThreadData(tname,i + 1,time(nullptr));
 44      pthread_create(tid + i,nullptr,thread_run,tp);
 45   }
 46 

 47   void* ret = nullptr;
 48   for (int i = 0;i < NUM;i++)
 49   {
 50     int n = pthread_join(tid[i],&ret);
 51     //errno变量只有一个,而线程有多个,作同时修改,可能会互相影响
 52     if(n!= 0)   cerr << "pthread_join error" << endl;
 53 
 54     cout << "thread quit: " << (uint64_t)ret << endl;
 55   }
 56   cout << "all thread quit" << endl;
 57   return 0;
 58 }

类型转换

在C,或者C++中,我们都知道,一个类型的值赋值给不匹配的类型变量,就会发生报错
但是,我们仔细思考一下,在计算机的眼里,不同数据有区别吗?
都只是0,1的集合罢了
所以,报错是编译器检测发现你类型不匹配,然后报错,显示无法编译你的代码
所谓的类型转换,就是让我们骗过编译器,让数据能够赋到我们想要的变量中
比如说下面的代码,1还是那个1,但是是int类型
你需要将它类型转换,告诉编译器,这个1其实是一个地址,这样才能成功赋值

void* ret = (void*)1;

进一步思考的话,类型转换也告诉了OS,这究竟是什么类型变量
这非常关键,决定我们将它存到哪里,它的偏移地址是什么等等,这样我们以后才能成功访问到这个数据

void*

所以,为什么无论是我们pthread_create函数,还是我们的pthread_exit函数,它们的参数中,设计的都是void*
为的是什么?
为的就是我们让我们传入参数和返回参数的可塑性更强,它并非局限我们只能传一个字符串作为线程函数传入参数,或者只能返回对应的错误码
我们是可以传int,double*等等所有的指针,甚至我们是可以传对象指针进去!!!*
只需要void*接收,然后再类型转换为我们想要的类型,就可以让OS找到对应的资源!!!
下面这段代码,就实现了传一个对象进去线程函数里面,并且通过返回这个对象的指针,将里面处理好的结果带出来

整段代码实现的功能
就是让不同的线程,分别实现从1到对应top数字的求和
原本的串行执行,转变为现在的并发执行

  1 #include <iostream>
  2 #include <unistd.h>
  3 #include <pthread.h>
  4 #include <string>
  5 #include <ctime>
  6 #define NUM 10
  7 using namespace std;
  8 enum{ ERROR = 0,OK };
  9 
 10 class ThreadData
 11 {
 12 public:
 13   ThreadData(const string& name,int id,time_t createTime,int top):_name(name),_id(id),_createTime((uint64_t)createTime),_status(OK),_top(top),_result(0)
 14   {}
 15   ~ThreadData()
 16   {}
 17 public:
 18     //传入的参数
 19     string _name;
 20     int _id;
 21     uint64_t _createTime;
 22     
 23     //返回的参数
 24     int _status; //该线程的参数
 25     int _top;
 26     int _result; //结果是什么
 27     //char arr[n];
 28 };
 29 void* thread_run(void* args)
 30 {                                                                                                                                                                   
 31   ThreadData* tp = static_cast<ThreadData*>(args);
 32 
 33   for (int i = 1;i <= tp->_top;i++)
 34   {
 35       tp->_result += i;
 36   }
 37 
 38   cout << "tp->_name: " << tp->_name << endl;
 39   return tp;
 40 }
 41 
 42 int main()
 43 {
 44   pthread_t tid[NUM];
 45   for (int i = 0;i < NUM;i++)
 46   {  
 47      char tname[64];
 48      snprintf(tname,64,"thread-%d",i + 1);
 49      //多传入一个参数,用来在创建线程,执行相应任务所加到的对应的数字
 50      ThreadData* tp = new ThreadData(tname,i + 1,time(nullptr),100 + 4*i);
 51      pthread_create(tid + i,nullptr,thread_run,tp);
 52      sleep(1);
 53   }
 54 
 55   void* ret = nullptr;
 56   for (int i = 0;i < NUM;i++)
 57   {
 58     int n = pthread_join(tid[i],&ret);
 59     //errno变量只有一个,而线程有多个,作同时修改,可能会互相影响
 60     if(n!= 0)   cerr << "pthread_join error" << endl;
 61     ThreadData* tp = static_cast<ThreadData*> (ret);
 62     if (tp->_status == OK)
 63     {
 64        cout << "thread name: " << tp->_name << " 计算的结果为:" << tp->_result << "[0," << tp->_top << "]" << endl;
 65     }
 66     delete tp;
 67   }
 68   cout << "all thread quit" << endl;
 69   return 0;
 70 }

输出的结果如下图所示:
在这里插入图片描述

让线程获取自己的线程id

那线程有自己的编号,能不能让线程获取对应自己的编号呢?
pthread库中也提供了相应的接口pthread_self
在这里插入图片描述
函数参数是没有的,直接调用即可输出当前线程的id是什么
我们可以编写一段程序,来看看对应的线程id,同时返回到主线程,也打印出来对比一下

  1 #include <iostream>
  2 #include <unistd.h>
  3 #include <pthread.h>
  4 #include <string>
  5 #include <ctime>
  6 #define NUM 10
  7 using namespace std;
  8 
  9 void* thread_create(void* args)
 10 {
 11    const char* name = static_cast<const char*>(args);
 12    int cnt = 5;
 13    while(cnt--)
 14    {
 15      cout << name << " is running..." << "  obtain my tid: " << pthread_self()<< endl;                                                                              
 16      sleep(1);
 17    }
 18 
 19    pthread_exit((void*)11);
 20 }
 21 int main()
 22 {
 23   pthread_t tid;
 24   pthread_create(&tid,nullptr,thread_create,(void*)"thread 1");
 25 
 26   void* ret = nullptr;
 27   int n = pthread_join(tid,&ret);
 28   if (n != 0)  cerr << "thread_join error: " << endl;
 29   cout << "new thread exit: " << (uint64_t)ret << endl;
 30   cout << " quit thread id: " << tid << endl;
 31   return 0;
 32 }

可以看到线程的id通过pthread_self函数,是能够成功获取的
在这里插入图片描述

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

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

相关文章

Qt_C++读写NFC标签Ntag支持windows国产linux操作系统

本示例使用的发卡器&#xff1a;Android Linux RFID读写器NFC发卡器WEB可编程NDEF文本/智能海报/-淘宝网 (taobao.com) ntag2标签存储结构说明 #include "mainwindow.h" #include "./ui_mainwindow.h" #include <QDebug> #include "QLibrary&…

IMU评估肌肉骨骼疾病风险

马来西亚是世界上最大的棕榈油生产国之一。棕榈工人使用凿子和镰刀收割棕榈树的动作&#xff0c;存在引起肌肉骨骼疾病(WMSDs)的风险。但大多数研究采用间接测量和定性方法来确定疼痛分布&#xff0c;这并不精确。在可穿戴传感器技术的最新进展中&#xff0c;惯性运动单元(IMU)…

9.26 牛客Java题库day 3

1.类变量&#xff08;static&#xff09;在不设置初始值时,会进行默认值赋值&#xff0c;而局部方法中声明的变量则必须进行初始化&#xff0c;它不会进行默认值赋值 2.了解forward,redirect: URL:统一资源定位符&#xff0c;又是也被俗称为网页地址 http://www.runoob.com/…

python 探索分形世界|曼德布洛特|np.frompyfunc()

文章目录 分形的重要特征曼德布洛特集合曼德布洛特集合有一个以证明的结论&#xff1a;图像展示np.ogrid[]np.frompyfunc()集合转图像 julia集合 无边的奇迹源自简单规则的无限重复 ---- 分形之父Benoit B.Mandelbrot 分形的重要特征 自相似性无标度性非线性 曼德布洛特集合…

大疆御3(DJI Mavic 3)照片格式,设置默认JPG格式

大疆御3(DJI Mavic 3)照片格式&#xff0c;设置默认JPG格式 一、照片格式。 御3提供两种照片格式&#xff0c;一种是常见的JPG格式&#xff1b;还有一种是DNG格式&#xff0c;这是一种无人机拍摄照片的原始格式&#xff0c;具有较高的图像质量和更多的后期处理空间&#xff0…

【乳腺超声、乳腺钼靶、宫颈癌】等项目数据调研,及相关参考内容整理汇总

一、乳腺超声内容整理 1.1、数据集 Breast Ultrasound Images Dataset;下载地址2STU-Hospital处理和训练参考文档:https://blog.csdn.net/weixin_51511389/article/details/127594654 1.2、可以参考的论文 AAU-net: An Adaptive Attention U-net for Breast Lesions Segmen…

GeoServer运行报错503,……Unmapped relationship: 7

Windows11运行GeoServer-2.19.0报错[org.geoserver.system.status.OSHISystemInfoCollector]……Unmapped relationship: 7 问题说明解决方法 问题说明 最近换了新电脑&#xff0c;在电脑上安装了一个geoserver2.19.0版本&#xff0c;但是运行就是报错&#xff0c;虽然最后提示…

乱收费被市场惩罚,互联网电视被用户抛弃,传统电视再度崛起!

洛图科技&#xff08;RUNTO&#xff09;公布了8月份国内电视市场的数据&#xff0c;数据显示互联网电视/智能电视被消费者抛弃导致出货量大跌&#xff0c;而传统电视品牌则获得了认可&#xff0c;显示出互联网电视乱收费正被市场惩罚。 洛图科技&#xff08;RUNTO&#xff09;公…

CV经典任务(一) 语义分割、实例分割 | 全卷积

文章目录 1 语义分割1.1 思路1 滑动窗口1.2 思路2 全卷积网络 2 代码实现3 实例分割 之前讲了分类 实际中除了分类还有几大视觉任务 语义分割&#xff0c;实例分割&#xff0c;目标检测 以上任务基本也都基于前面讲的卷积网络去做的 1 语义分割 语义分割&#xff08;Semant…

基于微信小程序的大学生科技竞赛竞技报名系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言系统主要功能&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计…

【高阶数据结构】红黑树(C++实现)

⭐博客主页&#xff1a;️CS semi主页 ⭐欢迎关注&#xff1a;点赞收藏留言 ⭐系列专栏&#xff1a;C进阶 ⭐代码仓库&#xff1a;C进阶 家人们更新不易&#xff0c;你们的点赞和关注对我而言十分重要&#xff0c;友友们麻烦多多点赞&#xff0b;关注&#xff0c;你们的支持是我…

Docker(三)、Dockerfile探究

Dockerfile探究 一、镜像层概念1、通过执行命令显化docker的机制 二、Dockerfile基础命令1、FROM 基于基准镜像【即构建镜像的时候&#xff0c;依托原有镜像做拓展】2、LABEL & MAINTAINER -说明信息3、WORKDIR 设置工作目录4、ADD & COPY 复制文件5、ENV 设置环境常量…

Java面向对象高级

文章目录 面向对象高级Object类的常用方法常用方法一&#xff08;面向对象阶段&#xff09;** 和 equals 的区别** 关键字native**单例设计模式&#xff08;Singleton&#xff09;**前情回顾&#xff08;学习基础&#xff09;静态修饰符Static设计模式概念开发步骤**两种实现方…

标准化、逻辑回归、随机梯度参数估计

机器学习入门 数据预处理&#xff1a; 将&#xff1f;替换为缺失值 data data.replace(to_replace"?",valuenp.nan)丢掉缺失值 data.dropna(how"any) #howall删除全是缺失值的行和列 #haowany删除有缺失值的行和列将数据集划分成测试集和训练集 data[colu…

自动混剪多段视频、合并音频、添加文案的技巧分享

在如今的社交媒体时代&#xff0c;视频的重要性越来越被人们所重视。许多人喜欢记录生活中的美好瞬间&#xff0c;并将其制作成视频分享给朋友和家人。然而&#xff0c;对于那些拍摄了大量视频的人来说&#xff0c;一个一个地进行剪辑和合并可能是一项令人头痛的任务。但是&…

Vue3最佳实践 第五章 Vue 组件应用 4 ( provide 和 inject )

5.5 provide 和 inject 前面的知识告诉我们vue中组件之间传递值需要使用props来完成&#xff0c;但是props也有一定局限性。这个时候在vue3中还有另外的解决方法。那就是使用 provide 和 inject 允许父组件将数据传递给所有后代组件&#xff0c;而不管组件层次结构有多深。你要…

CSS之伪类和伪元素 | :before和::before

例子&#xff1a; & 表示嵌套的上一级。如 &:hover 相当于 上一级元素:hover :hover 伪类 :before 伪元素&#xff0c;在元素之前加入某内容&#xff08;一定要写 content &#xff09; display:none&#xff1b; 隐藏对象。display隐藏元素后&#xff0c;不占原先位置…

如何开发物联网 APP?

如何开发物联网 APP? 这个问题本身是不严谨的&#xff0c;APP只是手机端的一个控制或者用于显示的人机交互页面&#xff0c;物联网是通过传感器&#xff0c;物联网卡等模块把物体接入网络以方便远程监控或者控制等。 你问的应该是怎么开发出来一个远程控制物体的APP吧&#x…

每日一练 | 网络工程师软考真题Day37

1、TCP协议在建立连接的过程中可能处于不同的状态&#xff0c;用netstat命令显示出TCP连接的状态为SYN_SEND&#xff0c;那么这个连接正处于 。 A&#xff0e;监听对方的建立连接请求 B&#xff0e;已主动发出连接建立请求 C&#xff0e;等待对方的连接释放请求 D&#xff…

cocoapods引擎插件所管理的开源库内新增声明文件 对外公开

cocoapods引擎插件所管理的开源库内新增声明文件&#xff0c;供外部业务层直接/间接访问 DemoDemo-Prefix.pch 备注&#xff1a;业务层项目(比如&#xff1a;BaseFramesDemo) target Build settings搜索Search Paths,然后点击它&#xff0c;看到Always Search User Paths&a…