Linux下的多线程编程:原理、工具及应用(1)

                                               🎬慕斯主页修仙—别有洞天

                                              ♈️今日夜电波:Flower of Life—陽花

                                                                0:34━━━━━━️💟──────── 4:46
                                                                    🔄   ◀️   ⏸   ▶️    ☰  

                                      💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍


目录

模拟语言封装Linux下多线程接口

线程互斥

前置知识

解释为什么会产生上述代码错误

如何解决?加锁!

什么是互斥锁?

pthread_mutex_t

pthread_mutex_init

PTHREAD_MUTEX_INITIALIZER

pthread_mutex_lock

pthread_mutex_unlock

pthread_mutex_trylock(不常用)

根据如上的互斥锁来进行操作

通过定义全局的锁

通过定义局部的锁(优雅的解决)


模拟语言封装Linux下多线程接口

        如下是我们以使用C++实现简单封装:

#pragma once
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <string>
#include <functional>template <class T>
using func_t = std::function<void(T)>;template <class T>
class Thread
{
public:Thread(const std::string threadname,func_t<T> func,T data):_tid(0),_threadname(threadname),_isrunning(false),_func(func),_data(data){}static void* ThreadRoutine(void* args){Thread *ts=static_cast<Thread *>(args);ts->_func(ts->_data);return nullptr;}bool Start(){int n=pthread_create(&_tid,nullptr,ThreadRoutine,this);if(n==0){_isrunning=true;return true;}return false;}bool Join(){if(!_isrunning) return true;int n=pthread_join(_tid,nullptr);if(n==0){_isrunning=false;return true;}return false;}std::string ThreadName(){return _threadname;}bool Isrunning(){return _isrunning;}private:pthread_t _tid;std::string _threadname;bool _isrunning;func_t<T> _func;T _data;
};

        其中成员变量存储了进程线程的ID、线程名、线程运行状态、线程运行的函数以及线程传递的变量。实现了线程的构造函数(根据线程名、传入函数以及传入变量构造),实现了开始运行的操作、等待线程的操作、判断是否运行以及返回线程名的操作

        重点说一下ThreadRoutine这个函数!他实现的是配合运行操作为pthread_create传递函数在执行完后return nullptr结束线程的操作!那他为啥要定义成如下的形式呢?

    static void* ThreadRoutine(void* args){Thread *ts=static_cast<Thread *>(args);ts->_func(ts->_data);return nullptr;}

        这是因为成员函数是默认会带有this指针的,而我们要传入pthread_create中规定了只能传入void* args变量的函数。因此我们使用static让他不具有this指针!当然,这并不是唯一的解决办法,我们也可以将他定义到类外,然后给在类内声明友元函数即可

线程互斥

        我们根据上面所封装的线程库来叙写了如下的代码:

#include "Thread.hpp"std::string GetThreadName()
{static int number = 1;char name[64];snprintf(name, sizeof(name), "Thread-%d", number++);return name;
}void Print(int num)
{while (num){std::cout << "hello world: " << num-- << std::endl;sleep(1);}
}int ticket = 10000; // 全局的共享资源void GetTicket(std::string name)
{while (true){if (ticket > 0) {// 充当抢票花费的时间usleep(1000);printf("%s get a ticket: %d\n", name.c_str(), ticket);ticket--;}else{break;}// 实际情况,还有后续的动作, TODO?}
}int main()
{std::string name1 = GetThreadName();Thread<std::string> t1(name1, GetTicket, name1);std::string name2 = GetThreadName();Thread<std::string> t2(name2, GetTicket, name2);std::string name3 = GetThreadName();Thread<std::string> t3(name3, GetTicket, name3);std::string name4 = GetThreadName();Thread<std::string> t4(name4, GetTicket, name4);t1.Start();t2.Start();t3.Start();t4.Start();t1.Join();t2.Join();t3.Join();t4.Join();return 0;
}

        该代码简单的模拟了一个抢票的场景,其中票数仅为tickets=10000,在tickets减为0的时候就会停止抢票。我们创建4个线程来模拟多人抢票的情况。按照代码的原意,四个线程会依次抢票知道票数为0为止,但是代码真的如我们所想的那样嘛?

        从上面的现象我们很容易的发现我们的共享资源tickets发生了不该发生的操作,出现抢到同一张票以及多抢了的情况,出现了数据不一致的情况!这是为什么呢?下面先了解一些前置知识:

前置知识

        我们将任何一个时刻,只允许一个进程正在访问的资源称为临界资源。把进程中访问临界资源的代码叫做临界区。例如我们上述的代码:

        互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用 。图解如下:

        原子性(后面讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成。

 

解释为什么会产生上述代码错误

        请先看以下代码:

#include<iostream>int main()
{int a = 10;a++;return 0;
}

        很平常的一段代码,但是这也是我们常出现错误代码中访问临界资源的代码。我们可以从反汇编中看出问题:

        可以看到通过反汇编,我们发现本来是一段代码的操作,在底层居然被翻译成了三条操作。这也就违背了我们的原子性!这三条语句在多线程并发访问的时候都有可能会被中断!大致图解:

        当我们知道实际上访问临界资源时是有三步操作后,我们就可以理解为什么会产生如上的错误了!看完如下的例子就明白了:假设现在我们启动了两个线程,他们都要执行如上的三步。线程1先是访问该临界资源,但是在他执行完几次完整的访问(3步都走完)后,它的时间片用完了,恰好此时该线程卡在第二步的--操作,此时他要保存上下文,eax中存储的值为7,然后轮到下一个进程执行。下一个进程也是执行完整了几步,恰好也是在第二步的时候他的时间片用完了,此时他也要保存上下文,eax中存储的值为3。接着轮到第一个线程,他需要恢复上下文啊,因此,从第三步开始将eax返回内存中,此时!count又变回了7!!!

        看完上面的例子你大概就明白了,为什么上述代码互产生错误的原因。因为多线程并发访问全部int,不是原子的!!!会有数据不一致的并发访问问题!

        看完上述的解释是不是以为完了?当然没有!我们都知道在CPU中我们存在着:算术运算、逻辑运算、处理内外中断、控制单元的操作。在上述出错代码中我们还存在着if的判断语句,这就是一种逻辑运算,底层是需要两步的处理:1、加载如寄存器。2、判断他也不是原子的!也会出现会有数据不一致的并发访问问题!因此,我们根本就不知道每个进程的具体执行状况!这也是为什么该数据会减到负数的原因,因为有可能if判断都认为是符合条件的!但是实际寄存器中的值确是不符合的!

如何解决?加锁!

什么是互斥锁?

        互斥锁是一种同步机制,用于确保在多线程环境中共享资源的安全访问

        互斥锁的核心作用是防止多个线程同时访问和修改共享资源,从而避免数据竞争和不一致的问题。以下是互斥锁的一些关键特性和概念:

  1. 排他性:互斥锁确保在任何时刻,只有一个线程能够持有锁并访问共享资源。当一个线程获得锁时,其他线程必须等待直到锁被释放。
  2. 同步原语:互斥锁是一种同步原语,它通常与条件变量、信号量等其他同步机制一起使用,以实现复杂的线程间协作和通信。
  3. 初始化:在使用互斥锁之前,需要对其进行初始化,这可以通过pthread_mutex_init函数完成,也可以静态地通过PTHREAD_MUTEX_INITIALIZER宏来初始化一个互斥锁变量。
  4. 加锁与解锁:线程在访问共享资源前需要对互斥锁进行加锁(上锁),访问完成后需要释放互斥锁(解锁)。这一过程通常通过pthread_mutex_lockpthread_mutex_unlock函数来实现。
  5. 性能考量:互斥锁可能导致进程睡眠和唤醒,以及上下文切换,这些都会带来一定的性能开销。因此,互斥锁适用于加锁时间较长的场景,以减少频繁的锁争用和上下文切换。
  6. 销毁:当互斥锁不再使用时,应当通过pthread_mutex_destroy函数进行销毁,以避免资源泄漏。

pthread_mutex_t

    pthread_mutex_t是一个数据类型,用于表示互斥锁(Mutex)对象。在多线程编程中,互斥锁是一种同步机制,用于保护共享资源,防止多个线程同时访问和修改这些资源,从而避免数据竞争和不一致的问题。

   pthread_mutex_t类型的变量通常用于声明一个互斥锁对象,并使用pthread_mutex_init函数进行初始化。初始化后,可以使用pthread_mutex_lock函数对互斥锁进行加锁操作,使用pthread_mutex_unlock函数进行解锁操作。

        需要注意的是,在使用完互斥锁后,应该及时销毁它,以避免资源泄漏。

pthread_mutex_init

    pthread_mutex_init是一个用于初始化互斥锁的函数,它是POSIX线程库(Pthreads)中的一部分。互斥锁是一种同步原语,用于保护共享资源,防止多个线程同时访问。

        函数原型:

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

        参数说明:

  • mutex:指向要初始化的互斥锁对象的指针。
  • attr:指向互斥锁属性对象的指针,可以设置为NULL,表示使用默认属性。

        返回值:

  • 成功时,返回0;
  • 失败时,返回一个非零错误码。

PTHREAD_MUTEX_INITIALIZER

    PTHREAD_MUTEX_INITIALIZER是一个宏定义,用于初始化一个互斥锁对象。它通常与静态分配的互斥锁变量一起使用,以确保在多线程环境中对共享资源的访问是安全的。

        该宏定义的作用是将互斥锁对象的值设置为默认状态,以便在程序启动时立即可用。具体来说,它将互斥锁对象的类型设置为pthread_mutex_t,并将其属性设置为默认值(通常是快速互斥锁)。

pthread_mutex_lock

    pthread_mutex_lock是一个函数,用于对互斥锁进行加锁操作。它的作用是确保在多线程环境中,只有一个线程可以访问共享资源,从而避免数据竞争和不一致的问题。

        函数原型:

int pthread_mutex_lock(pthread_mutex_t *mutex);

        其中,mutex参数是一个指向互斥锁对象的指针。

        当一个线程调用pthread_mutex_lock函数时,它会尝试获取互斥锁。如果互斥锁当前未被其他线程持有,则该线程成功获取互斥锁并继续执行后续代码。如果互斥锁已经被其他线程持有,则该线程会被阻塞,直到互斥锁被释放为止。

        一旦线程成功获取互斥锁,其他试图获取该互斥锁的线程将会被阻塞,直到当前持有互斥锁的线程调用pthread_mutex_unlock函数释放互斥锁。

        需要注意的是,在使用完互斥锁后,应该及时调用pthread_mutex_unlock函数来释放互斥锁,以避免死锁或资源泄漏的情况发生。

pthread_mutex_unlock

    pthread_mutex_unlock是一个函数,用于对互斥锁进行解锁操作。它的作用是释放当前线程持有的互斥锁,以便其他线程可以获取该互斥锁并访问共享资源。

        函数原型:

int pthread_mutex_unlock(pthread_mutex_t *mutex);

        其中,mutex参数是一个指向互斥锁对象的指针。

        当一个线程调用pthread_mutex_unlock函数时,它会尝试释放当前线程持有的互斥锁。如果当前线程确实持有该互斥锁,则该函数会成功释放互斥锁并返回0;否则,该函数会返回错误码。

        需要注意的是,在使用完互斥锁后,应该及时调用pthread_mutex_unlock函数来释放互斥锁,以避免死锁或资源泄漏的情况发生。

pthread_mutex_trylock(不常用)

    pthread_mutex_trylock是一个函数,用于尝试对互斥锁进行加锁操作。它的作用是尝试获取互斥锁,如果互斥锁当前未被其他线程持有,则该线程成功获取互斥锁并继续执行后续代码;如果互斥锁已经被其他线程持有,则该线程不会阻塞,而是立即返回错误码。

        函数原型:

int pthread_mutex_trylock(pthread_mutex_t *mutex);

        其中,mutex参数是一个指向互斥锁对象的指针。

        当一个线程调用pthread_mutex_trylock函数时,它会尝试获取互斥锁。如果互斥锁当前未被其他线程持有,则该线程成功获取互斥锁并继续执行后续代码;如果互斥锁已经被其他线程持有,则该线程不会阻塞,而是立即返回错误码。

        需要注意的是,在使用完互斥锁后,应该及时调用pthread_mutex_unlock函数来释放互斥锁,以避免死锁或资源泄漏的情况发生。

根据如上的互斥锁来进行操作

通过定义全局的锁

        如下我们根据互斥锁来解决上述的问题:

#include <iostream>
#include <unistd.h>
#include <vector>
#include <cstdio>
#include "Thread.hpp"// 应用方的视角
std::string GetThreadName()
{static int number = 1;char name[64];snprintf(name, sizeof(name), "Thread-%d", number++);return name;
}void Print(int num)
{while (num){std::cout << "hello world: " << num-- << std::endl;sleep(1);}
}int ticket = 10000; // 全局的共享资源
// 共享资源了
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 锁就有了,被定义并初始化了,这个锁也是全局的哦!!// 加锁:
// 1. 我们要尽可能的给少的代码块加锁
// 2. 一般加锁,都是给临界区加锁
void GetTicket(std::string name)
{while (true){// 2. 是由程序员自己保证的!规则都必须先申请锁// 3. 根据互斥的定义,任何时刻,只允许一个线程申请锁成功!多个线程申请锁失败,失败的线程怎么办?在mutex上进行阻塞,本质就是等待!pthread_mutex_lock(&mutex); // 1. 申请锁本身是安全的,原子的,为什么?if (ticket > 0) // 4. 一个线程在临界区中访问临界资源的时候,可不可能发生切换?可能,完全允许!!{// 充当抢票花费的时间usleep(1000);printf("%s get a ticket: %d\n", name.c_str(), ticket);ticket--;pthread_mutex_unlock(&mutex);}else{pthread_mutex_unlock(&mutex);break;}// 实际情况,还有后续的动作, TODO?}
}int main()
{std::string name1 = GetThreadName();Thread<std::string> t1(name1, GetTicket, name1);std::string name2 = GetThreadName();Thread<std::string> t2(name2, GetTicket, name2);std::string name3 = GetThreadName();Thread<std::string> t3(name3, GetTicket, name3);std::string name4 = GetThreadName();Thread<std::string> t4(name4, GetTicket, name4);t1.Start();t2.Start();t3.Start();t4.Start();t1.Join();t2.Join();t3.Join();t4.Join();return 0;
}

        可以看到我们成功的解决了上述的问题,但是加了互斥锁也让我们的执行速度相对于上面的执行速度变慢了许多。这是因为:当一个线程持有互斥锁时,其他试图访问相同共享资源的线程必须等待,直到锁被释放。这种等待会导致线程阻塞,减少了并行执行的机会。当线程在等待锁的过程中,操作系统可能会将其置于睡眠状态,并在锁可用时再次唤醒它。这种从睡眠到唤醒的过程涉及到上下文切换,这是一种相对耗时的操作。频繁的上下文切换会显著增加程序的执行时间。

通过定义局部的锁(优雅的解决)

        如下我们定义一个LockGuard.hpp的文件,该文件封装了一个可以通过构造以及析构完成对应的加锁以及解锁的操作:

#pragma once#include <pthread.h>// 不定义锁,默认认为外部会给我们传入锁对象
class Mutex
{
public:Mutex(pthread_mutex_t *lock):_lock(lock){}void Lock(){pthread_mutex_lock(_lock);}void Unlock(){pthread_mutex_unlock(_lock);}~Mutex(){}private:pthread_mutex_t *_lock;
};class LockGuard
{
public:LockGuard(pthread_mutex_t *lock): _mutex(lock){_mutex.Lock();}~LockGuard(){_mutex.Unlock();}
private:Mutex _mutex;
};

        主函数:

        可以看到我们在临界区中定义了上述LockGuard.hpp的lockguard变量,我们可以通过上述的构造以及析构进行加锁、解锁需要注意的是:临界区代码中可以发现我们使用了一个{}来括起来,这是表示代码块的意思,可以理解变量同在函数栈帧中一样。我们通过新定义的ThreadData类来传递给之前封装的多线程接口,优雅的实现了如下的代码:

#include <iostream>
#include <string>
#include <unistd.h>
#include <vector>
#include <cstdio>
#include "Thread.hpp"
#include "LockGuard.hpp"// 应用方的视角
std::string GetThreadName()
{static int number = 1;char name[64];snprintf(name, sizeof(name), "Thread-%d", number++);return name;
}void Print(int num)
{while (num){std::cout << "hello world: " << num-- << std::endl;sleep(1);}
}class ThreadData
{
public:ThreadData(const std::string &name, pthread_mutex_t *lock): threadname(name), pmutex(lock){}
public:std::string threadname;pthread_mutex_t *pmutex;
};int ticket = 10000; // 全局的共享资源void GetTicket(ThreadData *td)
{while (true){// 非临界区代码!// 2. 是由程序员自己保证的!规则都必须先申请锁// 3. 根据互斥的定义,任何时刻,只允许一个线程申请锁成功!多个线程申请锁失败,失败的线程怎么办?在mutex上进行阻塞,本质就是等待!{LockGuard lockguard(td->pmutex);if (ticket > 0) // 4. 一个线程在临界区中访问临界资源的时候,可不可能发生切换?可能,完全允许!!{// 充当抢票花费的时间usleep(1000);printf("%s get a ticket: %d\n", td->threadname.c_str(), ticket);ticket--;}else{break;}}// 非临界区代码!// 实际情况,还有后续的动作, TODO?}
}int main()
{pthread_mutex_t mutex;pthread_mutex_init(&mutex, nullptr);std::string name1 = GetThreadName();ThreadData *td = new ThreadData(name1, &mutex);Thread<ThreadData *> t1(name1, GetTicket, td);std::string name2 = GetThreadName();ThreadData *td2 = new ThreadData(name2, &mutex);Thread<ThreadData *> t2(name2, GetTicket, td2);std::string name3 = GetThreadName();ThreadData *td3 = new ThreadData(name3, &mutex);Thread<ThreadData *> t3(name3, GetTicket, td3);std::string name4 = GetThreadName();ThreadData *td4 = new ThreadData(name4, &mutex);Thread<ThreadData *> t4(name4, GetTicket, td4);t1.Start();t2.Start();t3.Start();t4.Start();t1.Join();t2.Join();t3.Join();t4.Join();pthread_mutex_destroy(&mutex);delete td;delete td2;delete td3;delete td4;return 0;
}

        一些知识点汇总:

// 加锁:
// 1. 我们要尽可能的给少的代码块加锁
// 2. 一般加锁,都是给临界区加锁
// 3. 个别系统,抢票代码会出现很多的票被同一个线程抢完了
// 4. 多线程运行,同一份资源,有线程长时间无法拥有,饥饿问题
// 5. 要解决饥饿问题,要让线程执行的时候,具备一定的顺序性 --- 同步

 


                         感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o! 

                                       

                                                                        给个三连再走嘛~  

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

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

相关文章

文献速递:深度学习乳腺癌诊断---使用深度学习改善乳腺癌组织学分级

Title 题目 Improved breast cancer histological grading using deep learning 使用深度学习改善乳腺癌组织学分级 01 文献速递介绍 乳腺癌组织学分级是乳腺癌中一个确立的临床变量&#xff0c;它包括来自三个方面的信息&#xff0c;即小管形成程度、核多态性和有丝分裂计…

IP证书有什么作用?怎么申请?

关于IP地址证书&#xff0c;它的主要作用有这么几个点&#xff1a; 1.验明正身&#xff1a;就像身份证一样&#xff0c;它可以证明某个服务器的IP地址是真的、合法的&#xff0c;让咱知道咱们连接的就是正确的服务器&#xff0c;而不是冒牌货。这样一来&#xff0c;就可以降低像…

【C++设计模式】策略模式

文章目录 前言一、策略模式是什么&#xff1f;二、策略模式的实现原理三、UML图四、代码实现总结 前言 策略模式是一种行为设计模式&#xff0c;它允许在运行时选择算法的行为。通过将每个算法封装到具有共同接口的独立类中&#xff0c;客户端可以在不改变自身代码的情况下选择…

Bugku MISC做题笔记

简单套娃DX 这一题需要对png图片的结构有所了解。详细可参考https://www.w3.org/TR/png/ 幸好每一张图片只有一个错误&#xff0c;逐步调试&#xff0c;就可以发现所有错误&#xff0c;修正即可。具体错误参看python程序中的注释&#xff1a; import ossrc_dir .\\XD\\ de…

微信每天通过好友上限是多少个呢?

微信每天通过好友上限是多少个呢&#xff1f; 1、新号和不活跃的号 微信新号是指注册不满15十五天&#xff0c;或者注册超过15天&#xff0c;但是没有好好养号的的账号。&#xff08;包括很多长期不活跃的账号&#xff0c;突然使用的情况&#xff09; 2、正常帐号 &#xf…

案例分析篇06:数据库设计相关28个考点(17~22)(2024年软考高级系统架构设计师冲刺知识点总结系列文章)

专栏系列文章推荐: 2024高级系统架构设计师备考资料(高频考点&真题&经验)https://blog.csdn.net/seeker1994/category_12593400.html 【历年案例分析真题考点汇总】与【专栏文章案例分析高频考点目录】(2024年软考高级系统架构设计师冲刺知识点总结-案例分析篇-…

Scala--01--简介、环境搭建

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1. Scala简介1.1 Scala是什么&#xff1f;官网&#xff1a; [https://scala-lang.org/](https://scala-lang.org/)官方文档&#xff1a; [https://docs.scala-lang.…

基于springboot+vue实现的大学计算机课程管理平台的设计与实现(全套资料)

一、系统架构 前端&#xff1a;vue | antv 后端&#xff1a;springboot | mybatis-plus 环境&#xff1a;jdk17 | mysql | maven | node | redis 二、代码及数据库 三、功能介绍 01. 登录页 02. 首页 03. 系统基础模块-用户管理 04. 系统基础模块-部门…

一个人做电商要怎么做?满足这三个条件,你也可以!

我是电商珠珠 现在电商平台很火&#xff0c;特别是短视频电商&#xff0c;很多从来没有接触过的新手会觉得自己一个人做不了&#xff0c;或者说投入成本大&#xff0c;会有很多人工费用&#xff0c;还要找货源找场地等。 其实&#xff0c;对于传统的电商来说&#xff0c;这些…

记一次Spring事务失效的发现与解决过程

一、事情起因是这样的 首先&#xff0c;我们是使用Spring mybatis 进行开发。 某功能在测试环境看到报错日志&#xff0c; 但是数据库里面的数据发生了变化&#xff0c;没有回滚。 执行数据库update 操作的方法上明确有 Transactional(rollbackFor Exception.class)的注解。…

蓝桥杯单片机快速开发笔记——HC573/HC138

一、原理分析 二、思维导图 三、代码参考 #include "HC573.h" #include "reg52.h"void Set_HC573(unsigned char channel, unsigned char dat) {P2 (P2 & 0x1f) | 0x00; //赋值之前&#xff0c;关闭全部锁存器P0 dat; //保存待设置…

阿里云和腾讯云2核4G服务器租用价格对比,选哪个好?

租用2核4G服务器费用多少&#xff1f;2核4G云服务器多少钱一年&#xff1f;1个月费用多少&#xff1f;阿里云2核4G服务器30元3个月、轻量应用服务器2核4G4M带宽165元一年、企业用户2核4G5M带宽199元一年&#xff1b;腾讯云轻量2核4G服务器5M带宽165元一年、252元15个月、540元三…

长期护理保险可改善老年人心理健康 | CHARLS CLHLS CFPS 公共数据库周报(3.6)...

欢迎报名2024年“真实世界临床研究”课程&#xff01; 本周郑老师开讲&#xff1a;“真实世界临床研究”培训班&#xff0c;3月16-17日两天&#xff0c;欢迎报名&#xff01; CHARLS公共数据库‍ CHARLS数据库简介中国健康与养老追踪调查(China Health and Retirement Longitud…

多模态特征融合新突破!5大方法刷新顶会SOTA!

多模态融合可以应用于情感分析、身份验证、行为识别等多个领域&#xff0c;实现更精确的预测和判断。这其中&#xff0c;特征融合是多模态数据处理的核心环节&#xff0c;它不仅能够提高模型的性能&#xff0c;还能够增强数据的表达力&#xff0c;让模型能够更好地理解和处理现…

案例分析篇05:数据库设计相关28个考点(9~16)(2024年软考高级系统架构设计师冲刺知识点总结系列文章)

专栏系列文章推荐: 2024高级系统架构设计师备考资料(高频考点&真题&经验)https://blog.csdn.net/seeker1994/category_12593400.html 【历年案例分析真题考点汇总】与【专栏文章案例分析高频考点目录】(2024年软考高级系统架构设计师冲刺知识点总结-案例分析篇-…

白嫖AWS云服务器,验证、注册指南

背景 不知道你想不想拥有一台属于自己的云服务器呢&#xff0c;拥有一台自己的云服务器可以建站&#xff0c;可以在上面搭建个人博客&#xff0c;今天我就来教大家如何申请亚马逊 AWS 免费云服务器&#xff0c;这个云服务器可以长达12个月的免费。而且到期后可以继续换个账号继…

测试人员业务提升的几个方面

&#x1f4cb; 个人简介 作者简介&#xff1a;大家好&#xff0c;我是凝小飞&#xff0c;软件测试领域作者支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; 整个文章来源于我的xmind梳理和总结&#xff0c;接下来为各位一步步拆解。 一、测试流程遵守和推进 在…

离线安装数据库 mysql 5.7 linux

离线安装数据库 mysql 5.7 linux 方法一 参考链接Linux(Debian10.2)安装MySQL5.7.24环境 赋予文件执行权限chmod x 文件名 使用root用户sudo su解压文件tar xvf mysql-5.7.42-linux-glibc2.12-x86_64.tar.gz重命名mv mysql-5.7.42-linux-glibc2.12-x86_64 mysql将桌面的mys…

使用 URLDecoder 和 URLEncoder 对中文字符进行编码和解码

请直接看原文: 使用 URLDecoder 和 URLEncoder 对中文字符进行编码和解码_urldecoder.decode-CSDN博客 ------------------------------------------------------------------------------------------------------------------------------- 摘要&#xff1a; URLDecoder 和…

plotnine,一个非常实用的 Python 库!

大家好&#xff0c;今天为大家分享一个非常实用的 Python 库 - plotnine。 Github地址&#xff1a;https://github.com/has2k1/plotnine 在数据分析和可视化领域&#xff0c;Python 提供了许多强大的工具和库。其中&#xff0c;plotnine 是一个基于 Grammar of Graphics 理论的…