Linux之线程安全(上)

news2024/11/18 3:31:29

文章目录

  • 前言
  • 一、预备知识
    • 1.线程的ID
    • 2.局部存储的验证
    • 3.线程的封装
  • 二、线程安全问题
    • 1.抢票程序
    • 2.问题分析
  • 三、Linux线程互斥
    • 1.概念
      • 临界资源
      • 临界区
      • 互斥
      • 原子性
    • 2.互斥量
      • 概念
      • 接口
    • 3.mutex的使用
      • 全局锁的使用
      • 局部锁的使用
  • 总结


前言

本文从一个模拟生活中的抢票程序的例子引入线程安全问题。


一、预备知识

1.线程的ID

用pthread_create创建一个线程,产生的线程ID存放在第一个参数之中,该线程ID和内核中的LWP不是一回事。pthread_create函数第一个参数指向一块虚拟内存单元,该内存单元的地址就是新创建线程ID,这个ID是线程库的范畴,而内核中LWP是进程调度的范畴,轻量级进程是OS调度的最小单位,需要一个数值来唯一标识该线程。
Linux并不提供真正的线程,只提供了LWP,但是程序员不关注LWP,只关注线程。因此,OS在OS与应用程序之间设计了一个原生线程库——pthread库。系统保存LWP,原生线程库可能存在多个线程,别人可以同时使用。OS只需要对内核执行流LWP进行管理,而提供给用户使用的线程接口等其他数据需要线程库自己来管理,线程库对线程的管理:先描述,再组织。
线程库实际上是一个动态库
在这里插入图片描述
进程运行时,动态库加载到内存,然后通过页表映射到进程地址空间的共享区,此时进程的所有线程都能看到这个动态库:
在这里插入图片描述

每个线程都有自己独立的栈:主线程采用的栈是进程地址空间中原生的栈,其他线程采用的是共享区中的栈。
每个线程都有自己的struct_pthread,包含对应线程的属性;每个线程都有自己的线程局部存储(添加__thread,可以将一个内置类型设置为线程局部存储),包含对应线程被切换时的上下文。
每个线程在共享区都有一块区域对该线程进行描述,因此我们要找得到一个用户级线程只需要找到该线程内存块的起始地址,就可以获取该线程的信息。
在这里插入图片描述

线程函数起始是在库内部对线程属性进行操作,最后将要执行的代码交给对应的内核级LWP去执行,因此线程数据的管理是在共享区。

线程ID本质上是进程地址空间共享区的一个虚拟地址。
文件test.c

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<pthread.h>
  4 void* start_routine(void* args)
  5 {
  6         while(1)
  7         {
  8                 printf("new thread tid:%p\n", pthread_self());
  9                 sleep(2);
 10         }
 11 }
 12 int main()
 13 {
 14         pthread_t tid;
 15         pthread_create(&tid, nullptr, start_routine, nullptr);
 16         while(1)
 17         {
 18                 printf("main thread tid:%p\n", pthread_self());
 19                 sleep(1);
 20         }
 21         return 0;
 22 }

在这里插入图片描述

2.局部存储的验证

设置一个全局变量g_val,让一个线程对它进行++操作,判断其他线程是否会受到影响:
文件test.c

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<pthread.h>
  4 int g_val = 10;
  5 void* start_routine(void* args)
  6 {
  7         const char* name = (const char*)args;
  8         while(1)
  9         {
 10                 printf("%s is running... g_val = %d, &g_val = %p\n",name, g_val, &g_val);
 11                 sleep(2);
 12                 ++g_val;
 13         }
 14 }
 15 int main()
 16 {
 17         pthread_t tid;
 18         pthread_create(&tid, nullptr, start_routine, nullptr);
 19         while(1)
 20         {
 21                 printf("main thread g_val:%d, &g_val = %p\n", g_val, &g_val);
 22                 sleep(1);
 23         }
 24         pthread_join(tid, nullptr);
 25         return 0;
 26 }

运行:
在这里插入图片描述
我们发现一个线程改变g_val会导致其它线程的g_val同步改变,即g_val所有线程共同只有一份。
给全局变量g_val加上__thread:
文件test.c

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<pthread.h>
  4 __thread int g_val = 10;
  5 void* start_routine(void* args)
  6 {
  7         const char* name = (const char*)args;
  8         while(1)
  9         {
 10                 printf("%s is running... g_val = %d, &g_val = %p\n",name, g_val, &g_val);
 11                 sleep(2);
 12                 ++g_val;
 13         }
 14 }
 15 int main()
 16 {
 17         pthread_t tid;
 18         pthread_create(&tid, nullptr, start_routine, nullptr);
 19         while(1)
 20         {
 21                 printf("main thread g_val:%d, &g_val = %p\n", g_val, &g_val);
 22                 sleep(1);
 23         }
 24         pthread_join(tid, nullptr);
 25         return 0;
 26 }

运行:
在这里插入图片描述
从运行结果来看,加上__thread的g_val就不在是共享的了,而是每个线程独有一份。添加__thread可以将一个内置类型设置为线程局部存储,使每个线程都有一份,介于全局变量和局部变量之间线程特有的属性。

3.线程的封装

如果我们想像C++一样使用线程,创建使用线程时,直接构造对象设置回调函数,对线程原生接口可以进行简单的封装:
文件Thread.hpp

  1 #include<iostream>
  2 using namespace std;
  3 #include<unistd.h>
  4 #include<pthread.h>
  5 #include<assert.h>
  6 #include<string>
  7 #include<string.h>
  8 #include<functional>
  9 class Thread;
 10 //上下文
 11 class Context
 12 {
 13 public:
 14         Thread* this_;
 15         void* args_;
 16 public:
 17         Context():this_(nullptr),args_(nullptr)
 18         {}
 19         ~Context()
 20         {}
 21 };
 22 
 23 //线程
 24 class Thread
 25 {
 26 public:
 27         typedef function<void*(void*)> func_t;
 28         const int num = 1024;
 29         Thread(func_t func, void* args, int number)
 30         :func_(func)
 31         ,args_(args)
 32         {
 33                 char buffer[num];
 34                 snprintf(buffer, sizeof buffer, "thread-%d", number);
 35                 name_ = buffer;
 36                 Context* ctx = new Context();
 37                 ctx -> this_ = this;
 38                 ctx -> args_ = args_;
 39                 int n = pthread_create(&tid_, nullptr, start_routine, ctx);
 40                 assert(n == 0);
 41                 (void)n;
 42         }
 43         static void* start_routine(void* args)
 44         {
 45                 Context* ctx = static_cast<Context*>(args);
 46                 void* ret = ctx -> this_ -> run(ctx -> args_);
 47                 delete ctx;
 48                 return ret;
 49         }
 50         void join()
 51         {
 52                 int n = pthread_join(tid_, nullptr);
 53                 assert(n == 0);
 54                 (void)n;
 55         }
 56         void* run(void* args)
 57         {
 58                 return func_(args);
 59         }
 60         ~Thread()
 61         {}
 62 private:
 63         string name_;
 64         pthread_t tid_;
 65         func_t func_;
 66         void* args_;
 67 };

二、线程安全问题

全局变量g_val可以被多个线程同时访问,多个线程同时操作可能会出现问题。

1.抢票程序

下面模拟抢票系统的抢票过程,多个线程同时对共享资源tickets做–的操作:
文件test.cc

  1 #include"Thread.hpp"
  2 #include<memory>
  3 int tickets = 1000;//票
  4 void* get_ticket(void* args)
  5 {
  6         string name = static_cast<const char*>(args);
  7         while(1)
  8         {
  9                 if(tickets > 0)
 10                 {
 11                         usleep(1234);//把调用该函数的线程挂起一段时间,单位是微秒(百万分之一秒)
 12                         cout<<name<<" 正在抢票,剩余票数"<<tickets<<endl;
 13                         tickets--;
 14                 }
 15                 else
 16                 {
 17                         break;
 18                 }
 19         }
 20         return nullptr;
 21 }
 22 int main()
 23 {
 24         std::unique_ptr<Thread> thread1(new Thread(get_ticket, (void*)"HelloThread", 1));
 25         unique_ptr<Thread> thread2(new Thread(get_ticket, (void*)"CoutThread", 2));
 26         unique_ptr<Thread> thread3(new Thread(get_ticket, (void*)"PrintThread", 3));
 27         unique_ptr<Thread> thread4(new Thread(get_ticket, (void*)"TestThread", 4));
 28         thread1->join();
 29         thread2->join();
 30         thread3->join();
 31         thread4->join();
 32         return 0;
 33 }

运行:
在这里插入图片描述
生活中抢票的剩余票数能是-1、-2吗?显然是不能的。

为啥结果会出现负数?
多个线程交叉执行任务。
多个线程交叉执行的本质:调度器尽可能的频繁发生线程调度与切换。
线程啥时候发生切换:时间片到了或者来啦更高优先级的线程或者线程等待的时候。
线程什么时候检测上面的问题(啥时候判断线程是否需要切换):从内核态返回用户态时,线程要对调度状态进行检测,如果满足上面的某个条件就发生线程切换。

在tickets = 1时,所有线程都可以进去:1.读取内存中tickets的值加载进CPU的寄存器中。2.判断tickets是大于0:第一个线程判断结束,将线程切换走(寄存器中有一个,里面放置的是当前执行流的上下文,当线程切换时会把上下文带走)此时还没进行–操作,因此其它线程看到的tickets也是1,因此其它进程判断tickets也是满足大于0的。3.在进行–操作之前会将线程挂起休眠一会,等线程被唤醒时线程会切换回来进行–操作。
–操作的本质是:1.读取数据、2.更改数据、3.写回数据。

2.问题分析

对一个全局变量进行多线程操作是不安全的
对该变量进行++或–操作,在高级语言层面上看是一条语句,但是经过汇编后至少是三条语句:
1.从内存读取数据到CPU寄存器中;2.在寄存器中让CPU进行对应的算术逻辑运算;3.将运算结果写回内存中该变量的位置(修改内存中的数据)
现在线程1将数据加载到寄存器中,进行–,数据变为999,到了写回这一步之前线程被切走了,当然是跟它的上下文一起被切走:
在这里插入图片描述
接着调度线程2,线程2可以运行的时间比较长,因此它可以一直进行–操作,直到数据减到100,线程2被切走了,跟着线程2的上下文一起。现在线程1回来了,线程1恢复自己的上下文,继续–的第三步操作,将内存里线程2好不容易–到的100恢复为了999。
在这里插入图片描述
在这里插入图片描述
由此可知定义的全局变量在没有任何保护的情况下,是不安全的。上面例子中,多线程交替执行造成了数据安全问题(数据不一致的问题)。
解决这种问题的办法就是加锁!!!

三、Linux线程互斥

1.概念

临界资源

多个执行流进行安全访问的共享资源,就是临界资源。

临界区

多个执行流进行访问临界资源的代码(上下文),称为临界区。

互斥

任何时刻,互斥保证有且仅有一个执行流进入临界区访问临界资源(可以对临界资源起保护作用)。

原子性

不会被任何调度机制打断的操作,因为该操作只有两态:要么做,要么不做。这就是原子性。

  1. 对一个变量进行操作,如果只有一条汇编语句就能完成,那么该操作就是原子的,反之就不是原子的。
    对变量++或–,在高级语言上来看是一条语句,但是汇编之后至少三条语句,因此它不是原子的。(会导致数据不一致的问题)
  2. 对一个资源进行访问,要么不做,要么做完。如果线程被切换导致没有做完访问的操作或者有中间状态,那么都不是原子的操作。

2.互斥量

概念

一般情况下,线程使用的变量都是局部变量,变量的地址空间在线程的栈空间内,这种情况,变量归属单个线程,其它线程无法获得这种变量。
也有以一些变量需要在线程间共享,这也的变量称为共享变量,可以通过它来进行线程间的交互。
多线程并发的操作共享变量(共享资源),可能会导致数据不一致的问题。为了解决该问题,我们要对共享资源做保护。

如何保护共享资源?
线程之间在并发操作共享资源时必须要互斥,当一个线程进入该资源的临界区时,不允许其它线程进入该临界区。
如果多个线程同时要求执行临界区的代码,并且此时临界区并没有线程进行执行,那么只允许其中一个线程进入该临界区。
如果线程不做临界区中,即线程已经执行完对该临界资源的操作,那么该线程就不能组织其它线程进入临界区。

如何保证互斥性呢?给临界资源加一把锁,只有持有该锁的线程可以进入临界区,其它线程无法访问临界区,直到对应线程将锁归还。Linux提供的这把锁就叫做互斥量(mutex)

接口

常见的相关接口

//初始化
int pthread_mutex_init(pthread_mutex_t* restrict mutex, const pthread_mutexattr_t* restrict attr);
//销毁
int pthread_mutex_destroy(pthread_mutex_t* mutex);
//全局
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//返回值:成功返回0,失败返回-1

锁既可以定义成局部的,也可以定义成全局的。
pthread_mutex_t是锁的类型,如果定义的锁是全局的就不需要用pthread_mutex_int和pthread_mutex_destroy初始化和销毁了。

//加锁
int pthread_mutex_lock(pthread_mutex_t* mutex);
//尝试加锁,如果加锁成功直接持有锁,如果加锁不成功立马出错返回(试着加锁,非阻塞获取锁)
int pthread_mutex_trylock(pthread_mutex_t* mutex);
//解锁
int pthread_mutex_unlock(pthread_mutex_t* mutex);

//返回值:成功返回0,失败返回错误码

3.mutex的使用

全局锁的使用

还是使用之前的抢票小进程,有四个线程进行抢票,我们给抢票的过程加上全局锁:
定义全局锁,并初始化PTHREAD_MUTEX_INITIALIZER,同时用pthread_create创建四个线程进行测试。因为此时的锁是全局锁,所以我们不用将锁传给每个线程。
文件test.cc

  1 #include"Thread.hpp"
  2 #include<memory>
  3 int tickets = 1000;//票
  4 pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
  5 void* get_ticket(void* args)
  6 {
  7         string name = static_cast<const char*>(args);
  8         while(1)
  9         {
 10                 pthread_mutex_lock(&lock);//加锁
 11                 if(tickets > 0)
 12                 {
 13                         usleep(1234);
 14                         cout<<name<<" 正在抢票,剩余票数"<<tickets<<endl;
 15                         tickets--;
 16                         pthread_mutex_unlock(&lock);//解锁
 17                 }
 18                 else
 19                 {
 20                         pthread_mutex_unlock(&lock);//解锁
 21                         break;
 22                 }
 23         }
 24         return nullptr;
 25 }
 26 int main()
 27 {
 28         std::unique_ptr<Thread> thread1(new Thread(get_ticket, (void*)"HelloThread", 1));
 29         unique_ptr<Thread> thread2(new Thread(get_ticket, (void*)"CoutThread", 2));
 30         unique_ptr<Thread> thread3(new Thread(get_ticket, (void*)"PrintThread", 3));
 31         unique_ptr<Thread> thread4(new Thread(get_ticket, (void*)"TestThread", 4));
 32         thread1->join();
 33         thread2->join();
 34         thread3->join();
 35         thread4->join();
 36         return 0;
 37 }

在这里插入图片描述

局部锁的使用

要用局部的锁,需要将锁传给每个线程,因此我们可以定义一个结构体ThreadData,存放着线程名与锁:
文件test.cc

  1 #include<iostream>
  2 using namespace std;
  3 #include<pthread.h>
  4 #include<string>
  5 #include<unistd.h>
  6 #include<vector>
  7 #define NUM 4
  8 int tickets = 1000;//票
  9 class ThreadData
 10 {
 11 public:
 12         ThreadData(const string& threadname, pthread_mutex_t* mutex_p)
 13         :threadname_(threadname)
 14         ,mutex_p_(mutex_p)
 15         {}
 16         ~ThreadData(){}
 17 public:
 18         string threadname_;
 19         pthread_mutex_t* mutex_p_;
 20 };
 21 void* get_ticket(void* args)
 22 {
 23         ThreadData* td = static_cast<ThreadData*>(args);
 24         while(1)
 25         {
 26                 pthread_mutex_lock(td -> mutex_p_);//加锁
 27                 if(tickets > 0)
 28                 {
 29                         usleep(1234);
 30                         cout<<td -> threadname_<<" 正在抢票,剩余票数"<<tickets<<endl;
 31                         tickets--;
 32                         pthread_mutex_unlock(td -> mutex_p_);//解锁
 33                 }
 34                 else
 35                 {
 36                         pthread_mutex_unlock(td -> mutex_p_);//解锁
 37                         break;
 38                 }
 39         }
 40         return nullptr;
 41 }
 42 int main()
 43 {
 44         pthread_mutex_t lock;
 45         pthread_mutex_init(&lock, nullptr);
 46         vector<pthread_t> tids(NUM);
 47         for(int i = 0;i < 4; ++i)
 48         {
 49                 char buffer[64];
 50                 snprintf(buffer, sizeof buffer, "thread %d", i + 1);
 51                 ThreadData* td = new ThreadData(buffer, &lock);
 52                 pthread_create(&tids[i], nullptr, get_ticket, td);
 53         }
 54         for(const auto &tid:tids)
 55         {
 56                 pthread_join(tid, nullptr);
 57         }
 58         pthread_mutex_destroy(&lock);
 59         return 0;
 60 }       

运行:
在这里插入图片描述
此时运行结果每次都是减到1,但是运行的速度变慢了。因为加锁和解锁的过程是多个线程串行执行的,因此程序运行就变慢了。
还有一个问题,我们发现此时的程序,每次都只有一个线程抢票。这是因为锁只规定了互斥访问,并没有规定谁先执行谁后执行(并没有规定优先级),因此是哪个线程的竞争力强就由哪个线程来持锁。
那么如何解决这个问题呢?
我们细想一下,生活中抢票就仅有抢到票的那一下就结束了吗?显然不是的,生活中抢到票之后还有后续工作要完成,比如填写具体信息、完成订单等等。
因此我们可以让抢票程序抢到之后休息一下,让线程usleep(1234),假设这段时间是系统在生成订单信息。
文件test.cc

  1 #include<iostream>
  2 using namespace std;
  3 #include<pthread.h>
  4 #include<string>
  5 #include<unistd.h>
  6 #include<vector>
  7 #define NUM 4
  8 int tickets = 1000;//票
  9 class ThreadData
 10 {
 11 public:
 12         ThreadData(const string& threadname, pthread_mutex_t* mutex_p)
 13         :threadname_(threadname)
 14         ,mutex_p_(mutex_p)
 15         {}
 16         ~ThreadData(){}
 17 public:
 18         string threadname_;
 19         pthread_mutex_t* mutex_p_;
 20 };
 21 void* get_ticket(void* args)
 22 {
 23         ThreadData* td = static_cast<ThreadData*>(args);
 24         while(1)
 25         {
 26                 pthread_mutex_lock(td -> mutex_p_);//加锁
 27                 if(tickets > 0)
 28                 {
 29                         usleep(1234);
 30                         cout<<td -> threadname_<<" 正在抢票,剩余票数"<<tickets<<endl;
 31                         tickets--;
 32                         pthread_mutex_unlock(td -> mutex_p_);//解锁
 33                 }
 34                 else
 35                 {
 36                         pthread_mutex_unlock(td -> mutex_p_);//解锁
 37                         break;
 38                 }
 39                 usleep(1234);//系统生成订单信息
 40         }
 41         return nullptr;
 42 }
 43 int main()
 44 {
 45         pthread_mutex_t lock;
 46         pthread_mutex_init(&lock, nullptr);
 47         vector<pthread_t> tids(NUM);
 48         for(int i = 0;i < 4; ++i)
 49         {
 50                 char buffer[64];
 51                 snprintf(buffer, sizeof buffer, "thread %d", i + 1);
 52                 ThreadData* td = new ThreadData(buffer, &lock);
 53                 pthread_create(&tids[i], nullptr, get_ticket, td);
 54         }
 55         for(const auto &tid:tids)
 56         {
 57                 pthread_join(tid, nullptr);
 58         }
 59         pthread_mutex_destroy(&lock);
 60         return 0;
 61 }

运行:
在这里插入图片描述
这样就解决了上面提出的问题。


总结

以上就是今天要讲的内容,本文介绍了线程安全的相关概念,从抢票系统引入线程安全问题,再一步步解决问题·。本文作者目前也是正在学习Linux相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。
最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!

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

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

相关文章

经典的设计模式22——职责链模式

文章目录 职责链模式 老感觉职责链模式和状态模式有点像&#xff0c;好像都能实现请假流程。百度来一波。 真是巧了&#xff0c;职责链&#xff0c;状态&#xff0c;策略&#xff0c;则三个长得好像。 职责链模式 定义&#xff1a; 使多个对象都有机会处理请求&#xff0c;从…

循环神经网络RNN用于分类任务

RNN是一类拥有隐藏状态&#xff0c;允许以前的输出可用于当前输入的神经网络&#xff0c; 输入一个序列&#xff0c;对于序列中的每个元素与前一个元素的隐藏状态一起作为RNN的输入&#xff0c;通过计算当前的输出和隐藏状态。当前的影藏状态作为下一个单元的输入... RNN的种类…

AMEYA:如何设计好DC-DC电源,注意事项有哪些

DC-DC变换器&#xff08;DC-DC converter&#xff09;是指在直流电路中将一个电压值的电能变为另一个电压值的电能的装置。DC-DC的layout非常重要&#xff0c;会直接影响到产品的稳定性与EMI效果。 DC-DC电源几点经验以及规则 1、处理好反馈环&#xff0c;反馈线不要走肖特基下…

基于JavaWeb的体育赛事平台的设计与实现

摘要 体育是随着社会生产力的发展而产生和发展的&#xff0c;在其漫长的历史中&#xff0c;由于社会、政治和经济发展的影响&#xff0c;其内容、形式、功能和操作方法不断变化。奥运会和世界杯等大型体育赛事代表着体育发展的顶峰&#xff0c;因为它们不仅给组织者带来了巨大…

【考研复习】李春葆新编C语言习题与解析(错误答案订正)持续更新

新编C语言习题与解析 做习题时发现有些错误答案&#xff0c;写篇博客进行改正记录。不对地方欢迎指正&#xff5e; 第二章 C. 其中b的表达形式错误&#xff0c;若加上0x1e2b则正确。所以C错误。 D. e后为整数。指数命名规则&#xff1a;e前有数&#xff0c;后有整数。所以D错…

实验篇(7.2) 15. 站对站安全隧道 - 多条隧道聚合(FortiGate-IPsec) ❀ 远程访问

【简介】虽然隧道冗余可以解决连接问题&#xff0c;但是当大量数据访问或要求访问不能中断时&#xff0c;隧道冗余就力不从心了。这种情况就要用到隧道聚合。但是对宽带的要求也高了&#xff0c;双端都至少需要二条宽带。 实验要求与环境 OldMei集团深圳总部部署了域服务器和ER…

C语言复合类型之结构(struct)篇(结构指针)

结构相关知识总结 什么是结构&#xff1f;结构的声明与简单使用结构的初始化结构中成员变量的访问结构的初始化器结构数组结构数组的声明结构数组的成员标识 结构的嵌套结构指针结构作为参数在函数中传递将结构成员作为参数进行传递将结构地址(指向结构的指针)作为参数进行传递…

AI数字人之语音驱动人脸模型Wav2Lip

1 Wav2Lip模型介绍 2020年&#xff0c;来自印度海德拉巴大学和英国巴斯大学的团队&#xff0c;在ACM MM2020发表了的一篇论文《A Lip Sync Expert Is All You Need for Speech to Lip Generation In The Wild 》&#xff0c;在文章中&#xff0c;他们提出一个叫做Wav2Lip的AI模…

面试题:完败的面试,被虐得体无完肤

经过上一轮的面试&#xff0c;我信心一下子就建立起来了&#xff0c;说巧不巧&#xff0c;前几周正好看到美团校招&#xff0c;想着试一下也不会怎样&#xff0c;就找了学长要了内推码&#xff0c;试着投递了一下&#xff0c;然后就通知周六参加笔试&#xff0c;结果惨不忍睹。…

flv 报错 Unsupported codec in video frame: 12

视频播放器播放 flv 报错 [TransmuxingController] > DemuxException: type CodecUnsupported, info Flv: Unsupported codec in video frame: 12 原因 主要是因为我们的播放器不支持 H.265 视频编码&#xff1b; 解决办法 方法一&#xff1a;将设备端的视频编码改为 …

FPGA实现USB3.0 UVC 相机HDMI视频输出 基于FT602驱动 提供工程源码和QT上位机源码

目录 1、前言2、UVC简介3、FT602芯片解读4、我这儿的 FT601 USB3.0通信方案5、详细设计方案基于FT602的UVC模块详解 6、vivado工程详解7、上板调试验证8、福利&#xff1a;工程代码的获取 1、前言 目前USB3.0的实现方案很多&#xff0c;但就简单好用的角度而言&#xff0c;FT6…

基于多层感知机MLP的数据预测与误差分析的完整matlab代码分享

多层感知机(MLP,Multilayer Perceptron)也叫人工神经网络(ANN,Artificial Neural Network),除了输入输出层,它中间可以有多个隐层,最简单的MLP只含一个隐层,即三层的结构。多层感知器(multilayer Perceptron,MLP)是指可以是感知器的人工神经元组成的多个层次。MPL的…

在Windows和Linux系统上,用C语言实现命令行下输入密码回显星号和完全隐藏密码

本篇目录 引子在Windows 上实现在Linux上实现回显星号代码解读运行 完全隐藏运行 引子 在Windows系统上&#xff0c;当我们使用命令行和MySQL进行交互时&#xff0c;第一步就是要输入密码&#xff1a; -p后面的参数紧跟着的就是相应用户的密码。然而这种方式并不安全&#xff…

【数学建模】2019 年全国大学生数学建模竞赛C题全国一等奖获奖论文

2021 年高教社杯全国大学生数学建模竞赛题目 机场的出粗车问题 大多数乘客下飞机后要去市区&#xff08;或周边&#xff09;的目的地&#xff0c;出租车是主要的交通工具之一。国内多数机场都是将送客&#xff08;出发&#xff09;与接客&#xff08;到达&#xff09;通道分开…

2. windows系统下在QT中配置OPenCV开发环境

1. 说明: 在Windows系统中配置相对简单,不需要对下载的源码进行编译,在官网上下载的OPenCV可以直接使用,本文系统版本为win10,opencv是最新版本4.7.0。 效果展示: 2. 配置步骤: 2.1 下载OPenCV压缩包 打开opencv的官网OPenCV下载地址,可以在其页面内下载到最新的压…

iPhone手机UDID获取方法

UDID&#xff1a;iOS设备的唯一识别码&#xff0c;每台iOS设备都有一个独一无二的编码&#xff0c;这个编码&#xff0c;就称为识别码&#xff0c;也叫做UDID&#xff08;Unique Device Identifier&#xff09; 一、通过Xcode查看 手机连接电脑打开Xcode&#xff0c;选择wind…

入职2个月,那个高薪挖来的自动化软件测试被劝退了....

其实&#xff0c;在很多小伙伴的想法中&#xff0c;是希望通过跳槽实现薪酬涨幅&#xff0c;可是跳槽不是冲动后决定&#xff0c;应该谨慎啊~ 01 我的学弟&#xff0c;最近向我吐槽&#xff0c;2020 年上半年入职一家公司&#xff0c;当时是高薪挖走的他&#xff0c;所谓钱到…

阿里云无影云电脑使用教程全流程(5分钟上手)

阿里云无影云电脑即无影云桌面&#xff0c;云桌面如何使用&#xff1f;云桌面购买后没有用户名和密码&#xff0c;先创建用户设置密码&#xff0c;才可以登录连接到云桌面。云桌面想要访问公网还需要开通互联网访问功能。阿里云百科来详细说下阿里云无影云电脑从购买、创建用户…

h264结构与码流

h264基本概念结构图 H264视频压缩后会成为一个序列帧&#xff0c;帧里包含图像&#xff0c;图像分为很多片&#xff0c;每个片可以分为宏块&#xff0c;每个宏块由许多子块组成 H264结构中&#xff0c;一个视频图像编码后的数据叫做一帧&#xff0c;一帧由一个片&#xff08;sl…

Redis系列--布隆过滤器(Bloom Filter)

一、前言 在实际开发中&#xff0c;会遇到很多要判断一个元素是否在某个集合中的业务场景&#xff0c;类似于垃圾邮件的识别&#xff0c;恶意ip地址的访问&#xff0c;缓存穿透等情况。类似于缓存穿透这种情况&#xff0c;有许多的解决方法&#xff0c;如&#xff1a;redis存储…