Qt5开发及实例V2.0-第十二章-Qt多线程

2023-09-22 05:15:00

第12章 Qt 5多线程

多线程具有以下几点优势。
(1)提高应用程序的响应速度。这对于开发图形界面的程序尤为重要,当一个操作耗时很长时,整个系统都会等待这个操作,程序就不能响应键盘、鼠标、菜单等的操作,而使用多线程技术可将耗时长的操作置于一个新的线程,从而避免以上的问题。
(2)使多CPU系统更加有效。当线程数不大于CPU数目时,操作系统可以调度不同的线程运行于不同的CPU上。
(3)改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为独立或半独立的运行部分,这样有利于代码的理解和维护。

多线程程序有以下几个特点。
(1)多线程程序的行为无法预期,当多次执行上述程序时,每一次的运行结果都可能不同。
(2)多线程的执行顺序无法保证,它与操作系统的调度策略和线程优先级等因素有关。
(3)多线程的切换可能发生在任何时刻、任何地点。
(4)多线程对代码的敏感度高,因此对代码的细微修改都可能产生意想不到的结果。

12.1 多线程及简单实例

【例】(难度一般)(CH1201)如图12.1所示,单击“开始”按钮将启动数个工作线程(工作线程数目由MAXSIZE宏决定),各个线程循环打印数字0~9,直到单击“停止”按钮终止所有线程为止。
实现步骤如下。
(1)在头文件“threaddlg.h”中声明用于界面显示所需的控件,其具体代码如下:

#include <QDialog>
#include <QPushButton>
class ThreadDlg : public QDialog
{
    Q_OBJECT
public:
    ThreadDlg(QWidget *parent = 0);
    ~ThreadDlg();
private:
    QPushButton *startBtn;
    QPushButton *stopBtn;
    QPushButton *quitBtn;
};

(2)在源文件“threaddlg.cpp”的构造函数中,完成各个控件的初始化工作,其具体代码如下:

#include "threaddlg.h"
#include <QHBoxLayout>
ThreadDlg::ThreadDlg(QWidget *parent)
    : QDialog(parent)
{
    setWindowTitle(tr("线程"));
    startBtn = new QPushButton(tr("开始"));
    stopBtn = new QPushButton(tr("停止"));
    quitBtn = new QPushButton(tr("退出"));
    QHBoxLayout *mainLayout = new QHBoxLayout(this);
    mainLayout->addWidget(startBtn);
    mainLayout->addWidget(stopBtn);
    mainLayout->addWidget(quitBtn);
}

(3)此时运行程序,界面显示如图12.1所示。
在这里插入图片描述
以上完成了界面的设计,下面的内容是具体的功能实现。
(1)在头文件“workthread.h”中,工作线程WorkThread类继承自QThread类。重新实现run()函数。其具体代码如下:

#include <QThread>
class WorkThread : public QThread
{
    Q_OBJECT
public:
    WorkThread();
protected:
    void run();
};

(2)在源文件“workthread.cpp”中添加具体实现代码如下:

#include "workthread.h"
#include <QtDebug>
WorkThread::WorkThread()
{
}

run()函数实际上是一个死循环,它不停地打印数字0~9。为了显示效果明显,程序将每一个数字重复打印8次。

void WorkThread::run()
{
    while(true)
    {
        for(int n=0;n<10;n++)
            qDebug()<<n<<n<<n<<n<<n<<n<<n<<n;
    }
}

(3)在头文件“threaddlg.h”中添加以下内容:

#include "workthread.h"
#define MAXSIZE 1			//MAXSIZE宏定义了线程的数目
public slots:
    void slotStart();				//槽函数用于启动线程
    void slotStop();				//槽函数用于终止线程
private:
WorkThread *workThread[MAXSIZE];		//(a)

(4)在源文件“threaddlg.cpp”中添加以下内容。
其中,在构造函数中添加如下代码:

connect(startBtn,SIGNAL(clicked()),this,SLOT(slotStart()));
connect(stopBtn,SIGNAL(clicked()),this,SLOT(slotStop()));
connect(quitBtn,SIGNAL(clicked()),this,SLOT(close()));

槽函数slotStart(),当用户单击“开始”按钮时,此函数将被调用。这里使用两个循环,目的是为了使新建的线程尽可能同时开始执行,其具体实现代码如下:

void ThreadDlg::slotStart()
{
    for(int i=0;i<MAXSIZE;i++)
    {
        workThread[i]=new WorkThread();		//(a)
    }
    for(int i=0;i<MAXSIZE;i++)
    {
        workThread[i]->start();			//(b)
    }
    startBtn->setEnabled(false);
    stopBtn->setEnabled(true);
}

槽函数slotStop(),当用户单击“停止”按钮时,此函数将被调用。其具体实现代码如下:

void ThreadDlg::slotStop()
{
    for(int i=0;i<MAXSIZE;i++)
    {
        workThread[i]->terminate();
        workThread[i]->wait();
    }
    startBtn->setEnabled(true);
    stopBtn->setEnabled(false);
}

(5)运行结果如图12.2所示。
在这里插入图片描述

12.2 多线程控制

实现线程的互斥与同步常使用的类有QMutex、QMutexLocker、QReadWriteLocker、QReadLocker、QWriteLocker、QSemaphore和QWaitCondition。
下面举一个例子来说明问题:

class Key
{
public:
    Key() {key=0;}
    int creatKey() {++key; return key;}
    int value()const {return key;}
private:
    int key;    
};

虽然类Key产生主键的函数creatKey()只有一条语句执行修改成员变量key的值,但是C++的“++”操作符并不是原子操作,通常编译后,它将被展开成为以下三条机器命令:
 将变量值载入寄存器。
 将寄存器中的值加1。
 将寄存器中的值写回主存。
假设当前的key值为0,如果线程1和线程2同时将0值载入寄存器,执行加1操作并将加1后的值写回主存,则结果是两个线程的执行结果将互相覆盖,实际上仅进行了一次加1操作,此时的key值为1。

12.2.1 互斥量

1.QMutex类
QMutex类是对互斥量的处理。它被用来保护一段临界区代码,即每次只允许一个线程访问这段代码。
QMutex类的lock()函数用于锁住互斥量。如果互斥量处于解锁状态,则当前线程就会立即抓住并锁定它,否则当前线程就会被阻塞,直到持有这个互斥量的线程对它解锁。线程调用lock()函数后就会持有这个互斥量,直到调用unlock()操作为止。
QMutex类还提供了一个tryLock()函数。如果互斥量已被锁定,则立即返回。
例如:

class Key
{
public:
    Key() {key=0;}
    int creatKey() { mutex.lock();  ++key;  return key;  mutex. unlock();}
    int value()const { mutex.lock();  return key;  mutex.unlock();}
private:
    int key;
    QMutex mutex;
};

2.QMutexLocker类
Qt提供的QMutexLocker类可以简化互斥量的处理,它在构造函数中接收一个QMutex对象作为参数并将其锁定,在析构函数中解锁这个互斥量,这样就解决了以上问题。
例如:

class Key
{
public:
    Key() {key=0;}
    int creatKey() { QmutexLocker locker(&mutex);  ++key;  return key; }
    int value()const { QmutexLocker locker(&mutex);  return key; }
private:
    int key;
    QMutex mutex;
};

12.2.2 信号量

生产者/消费者实例中对同步的需求有两处:
(1)如果生产者过快地生产数据,将会覆盖消费者还没有读取的数据。
(2)如果消费者过快地读取数据,将越过生产者并且读取到一些过期数据。
针对以上问题,可以有两种解决方法:
(1)首先使生产者填满整个缓冲区,然后等待消费者读取整个缓冲区,这是一种比较笨拙的方法。
(2)使生产者和消费者线程同时分别操作缓冲区的不同部分,这是一种比较高效的方法。

【例】(难度一般)(CH1202)基于控制台程序实现。
(1)源文件“main.cpp”中添加的具体实现代码如下:

#include <QCoreApplication>
#include <QSemaphore>
#include <QThread>
#include <stdio.h>
const int DataSize=1000;
const int BufferSize=80;
int buffer[BufferSize];					//(a)
QSemaphore freeBytes(BufferSize);			//(b)
QSemaphore usedBytes(0);				//(c)

(2)Producer类继承自QThread类,作为生产者类,其声明如下:

class Producer : public QThread
{
public:
    Producer();
    void run();
};

Producer构造函数中没有实现任何内容:

Producer::Producer()
{
}

Producer::run()函数的具体实现代码如下:

void Producer::run()
{
    for(int i=0;i<DataSize;i++)
    {
       freeBytes.acquire();				//(a)
       buffer[i%BufferSize]=(i%BufferSize);			//(b)
       usedBytes.release();				//(c)
    }
}

(3)Consumer类继承自QThread类,作为消费者类,其声明如下:

class Consumer : public QThread
{
public:
    Consumer();
    void run();
};

Consumer构造函数中没有实现任何内容:

Consumer::Consumer()
{
}

Consumer::run()函数的具体实现代码如下:

void Consumer::run()
{
    for(int i=0;i<DataSize;i++)
    {
       usedBytes.acquire();							//(a)
       fprintf(stderr,"%d",buffer[i%BufferSize]);	//(b)
       if(i%16==0&&i!=0)
       fprintf(stderr,"\n");
       freeBytes.release();							//(c)
    }
    fprintf(stderr,"\n");
}

(4)main()函数的具体内容如下:

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    Producer producer;
    Consumer consumer;
	/* 启动生产者和消费者线程 */
    producer.start();
    consumer.start();
	/* 等待生产者和消费者各自执行完毕后自动退出 */
    producer.wait();
    consumer.wait();
    return a.exec();
}

(5)最终运行结果如图12.3所示。
在这里插入图片描述

12.2.3 线程等待与唤醒

【例】(难度一般)(CH1203)使用QWaitCondition类解决生产者和消费者问题。
源文件“main.cpp”的具体内容如下:

#include <QCoreApplication>
#include <QWaitCondition>
#include <QMutex>
#include <QThread>
#include <stdio.h>
const int DataSize=1000;
const int BufferSize=80;
int buffer[BufferSize];
QWaitCondition bufferEmpty;
QWaitCondition bufferFull;
QMutex mutex;				//(a)
int numUsedBytes=0;				//(b)
int rIndex=0;					//(c)

生产者线程Producer类继承自QThread类,其声明如下:

class Producer : public QThread
{
public:
    Producer();
    void run();
};

Producer构造函数无须实现:

Producer::Producer()
{
}

Producer::run()函数的具体内容如下:

void Producer::run()
{
    for(int i=0;i<DataSize;i++)				//(a)
    {
       mutex.lock();
       if(numUsedBytes==BufferSize)			//(b)
          bufferEmpty.wait(&mutex);			//(c)
       buffer[i%BufferSize]=numUsedBytes;		//(d)
       ++numUsedBytes;						//增加numUsedBytes变量
       bufferFull.wakeAll();				//(e)
       mutex.unlock();
    }
}

其中,
(a) for(int i=0;i<DataSize;i++) { mutex.lock(); … mutex.unlock();}:for循环中的所有语句都需要使用互斥量加以保护,以保证其操作的原子性。
(b) if(numUsedBytes==BufferSize):首先检查缓冲区是否已经填满。
© bufferEmpty.wait(&mutex):如果缓冲区已经填满,则等待“缓冲区有空位”(bufferEmpty变量)条件成立。wait()函数将互斥量解锁并在此等待,其原型如下:

bool QWaitCondition::wait
(
	QMutex * mutex,
 	unsigned long time = ULONG_MAX
)

(d) buffer[i%BufferSize]=numUsedBytes:如果缓冲区未被填满,则向缓冲区中写入一个整数值。
(e) bufferFull.wakeAll():最后唤醒等待“缓冲区有可用数据”(bufferEmpty变量)条件为“真”的线程。

消费者线程Consumer类继承自QThread类,其声明如下:

class Consumer : public QThread
{
public:
    Consumer();
    void run();
};

Consumer构造函数中无须实现内容:

Consumer::Consumer()
{
}

Consumer::run()函数的具体内容如下:

void Consumer::run()
{
    forever
    {
        mutex.lock();
        if(numUsedBytes==0)
            bufferFull.wait(&mutex);			//(a)
        printf("%ul::[%d]=%d\n",currentThreadId(),rIndex,buffer[rIndex]);
						//(b)
        rIndex=(++rIndex)%BufferSize;		//将rIndex变量循环加1
        --numUsedBytes;				//(c)
        bufferEmpty.wakeAll();			//(d)
        mutex.unlock();
    }
    printf("\n");
}

main()函数的具体内容如下:

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    Producer producer;
    Consumer consumerA;
    Consumer consumerB;
    producer.start();
    consumerA.start();
    consumerB.start();
    producer.wait();
    consumerA.wait();
    consumerB.wait();
    return a.exec();
}

程序最终的运行结果如图12.4所示。
在这里插入图片描述

12.3 多线程应用

12.3.1 【实例】:服务器编程

【例】(难度中等)(CH1204)服务器编程。
首先,建立服务器端工程“TimeServer.pro”。文件代码如下。
(1)在头文件“dialog.h”中,定义服务器端界面类Dialog继承自QDialog类,其具体代码如下:

#include <QDialog>
#include <QLabel>
#include <QPushButton>
class Dialog : public QDialog
{
    Q_OBJECT
public:
    Dialog(QWidget *parent = 0);
    ~Dialog();
private:
    QLabel *Label1;				//此标签用于显示监听端口
    QLabel *Label2;				//此标签用于显示请求次数
    QPushButton *quitBtn;				//退出按钮
};

(2)在源文件“dialog.cpp”中,Dialog类的构造函数完成了初始化界面,其具体代码如下:

#include "dialog.h"
#include <QHBoxLayout>
#include <QVBoxLayout>
Dialog::Dialog(QWidget *parent)
    : QDialog(parent)
{
    setWindowTitle(tr("多线程时间服务器"));
    Label1 =new QLabel(tr("服务器端口:"));
    Label2 = new QLabel;
    quitBtn = new QPushButton(tr("退出"));
    QHBoxLayout *BtnLayout = new QHBoxLayout;
    BtnLayout->addStretch(1);
    BtnLayout->addWidget(quitBtn);
    BtnLayout->addStretch(1);
    QVBoxLayout *mainLayout = new QVBoxLayout(this);
    mainLayout->addWidget(Label1);
    mainLayout->addWidget(Label2);
    mainLayout->addLayout(BtnLayout);
    connect(quitBtn,SIGNAL(clicked()),this,SLOT(close()));
}

(3)此时运行服务器端工程“TimeServer.pro”,界面显示如图12.5所示。
在这里插入图片描述

(4)在服务器端工程“TimeServer.pro”中,添加C++ Class文件“timethread.h”及“timethread.cpp”。在头文件“timethread.h”中,工作线程TimeThread类继承自QThread类,实现TCP套接字,其具体代码如下:

#include <QThread>
#include <QtNetwork>
#include <QTcpSocket>
class TimeThread : public QThread
{
    Q_OBJECT
public:
    TimeThread(int socketDescriptor,QObject *parent=0);
    void run();					//重写此虚函数
signals:
    void error(QTcpSocket::SocketError socketError);	//出错信号
private:
    int socketDescriptor;				//套接字描述符
};

(5)在源文件“timethread.cpp”中,TimeThread类的构造函数只是初始化了套接字描述符,其具体代码如下:

#include "timethread.h"
#include <QDateTime>
#include <QByteArray>
#include <QDataStream>
TimeThread::TimeThread(int socketDescriptor,QObject *parent)
    :QThread(parent),socketDescriptor(socketDescriptor)
{
}

TimeThread::run()函数是工作线程(TimeThread)的实质所在,当在TimeServer:: incomingConnection()函数中调用了thread->start()函数后,此虚函数开始执行,其具体代码如下:

void TimeThread::run()
{
    QTcpSocket tcpSocket;			//创建一个QTcpSocket类
    if(!tcpSocket.setSocketDescriptor(socketDescriptor))	//(a)
    {
        emit error(tcpSocket.error());			//(b)
        return;
    }
    QByteArray block;
    QDataStream out(&block,QIODevice::WriteOnly);
    out.setVersion(QDataStream::Qt_5_8);
    uint time2u = QDateTime::currentDateTime().toTime_t();//(c)
    out<<time2u;
    tcpSocket.write(block);			//将获得的当前时间传回客户端
    tcpSocket.disconnectFromHost();	//断开连接
    tcpSocket.waitForDisconnected();	//等待返回
}

(6)在服务器端工程“TimeServer.pro”中添加C++ Class文件“timeserver.h”及“timeserver.cpp”。在头文件“timeserver.h”中,实现了一个TCP服务器端,类TimeServer继承自QTcpServer类,其具体代码如下:

#include <QTcpServer>
class Dialog;                         			//服务器端的声明
class TimeServer : public QTcpServer
{
    Q_OBJECT
public:
    TimeServer(QObject *parent=0);
protected:
    void incomingConnection(int socketDescriptor);	//(a)
private:
    Dialog *dlg;					//(b)
};

(7)在源文件“timeserver.cpp”中,构造函数只是用传入的父类指针parent初始化私有变量dlg,其具体代码如下:

#include "timeserver.h"
#include "timethread.h"   
#include "dialog.h"
TimeServer::TimeServer(QObject *parent):QTcpServer(parent)
{
    dlg =(Dialog *)parent;
}

重写的虚函数incomingConnection()的具体代码如下:

void TimeServer::incomingConnection(int socketDescriptor)
{
    TimeThread *thread = new TimeThread(socketDescriptor,0);	//(a)
    connect(thread,SIGNAL(finished()),dlg,SLOT(slotShow()));	//(b)
    connect(thread,SIGNAL(finished()),thread,SLOT(deleteLater()),
            Qt::DirectConnection);				//(c)
    thread->start();					//(d)
}

(8)在服务器端界面的头文件“dialog.h”中添加的具体代码如下:

class TimeServer;
public slots:
    void slotShow();			//此槽函数用于界面上显示的请求次数
private:
    TimeServer *timeServer;		//TCP服务器端timeServer
    int count;				//请求次数计数器count

(9)在源文件“dialog.cpp”中,添加的头文件如下:

#include <QMessageBox>
#include "timeserver.h"

其中,在Dialog类的构造函数中添加的内容,用于启动服务器端的网络监听,其具体实现如下:

count=0;
timeServer = new TimeServer(this);
if(!timeServer->listen())
{
    QMessageBox::critical(this,tr("多线程时间服务器"),
    tr("无法启动服务器:%1.").arg(timeServer->errorString()));
    close();
    return;
}

Label1->setText(tr(“服务器端口:%1.”).arg(timeServer->serverPort()));
在源文件“dialog.cpp”中,槽函数slotShow()的具体内容如下:

void Dialog::slotShow()
{
    Label2->setText(tr("第%1次请求完毕。").arg(++count));
}

(10)在服务器端工程文件“TimeServer.pro”中添加如下代码:

QT += network

(11)最后运行服务器端工程“TimeServer.pro”,结果如图12.6所示。
在这里插入图片描述

12.3.2 【实例】:客户端编程

【例】(难度中等)(CH1205)客户端编程。界面效果如图12.7所示。
在这里插入图片描述

操作步骤如下。
(1)建立客户端工程“TimeClient.pro”。在头文件“timeclient.h”中,定义了客户端界面类TimeClient继承自QDialog类,其具体代码。
(2)在源文件“timeclient.cpp”中,TimeClient类的构造函数完成了初始化界面,其具体代码。
在源文件“timeclient.cpp”中,enableGetBtn()函数的具体代码如下:

void TimeClient::enableGetBtn()
{
     getBtn->setEnabled(!serverNameLineEdit->text().isEmpty()&&
          !portLineEdit->text().isEmpty());
}

在源文件“timeclient.cpp”中,getTime()函数的具体代码如下:

void TimeClient::getTime()
{
    getBtn->setEnabled(false);
    time2u =0;
    tcpSocket->abort();
    tcpSocket->connectToHost(serverNameLineEdit->text(),
        portLineEdit->text().toInt());
}

在源文件“timeclient.cpp”中,readTime ()函数的具体代码如下:

void TimeClient::readTime()
{
    QDataStream in(tcpSocket);
    in.setVersion(QDataStream::Qt_5_8);
    if(time2u==0)
    {
        if(tcpSocket->bytesAvailable()<(int)sizeof(uint))
            return;
        in>>time2u;
    }
    dateTimeEdit->setDateTime(QDateTime::fromTime_t(time2u));
    getBtn->setEnabled(true);
}

在源文件“timeclient.cpp”中,showError()函数的具体代码如下:

void TimeClient::showError(QAbstractSocket::SocketError socketError)
{
    switch (socketError)
    {
    case QAbstractSocket::RemoteHostClosedError:
        break;
    case QAbstractSocket::HostNotFoundError:
        QMessageBox::information(this, tr("时间服务客户端"),
             tr("主机不可达!"));
        break;
    case QAbstractSocket::ConnectionRefusedError:
        QMessageBox::information(this, tr("时间服务客户端"),
             tr("连接被拒绝!"));
        break;
    default:
        QMessageBox::information(this, tr("时间服务客户端"),
              tr("产生如下错误: %1.").arg(tcpSocket->errorString()));
    }
    getBtn->setEnabled(true);
}

(3)在客户端工程文件“TimeClient.pro”中,添加如下代码:

QT += network

(4)运行客户端工程“TimeClient.pro”,显示界面如图12.7所示。
最后,同时运行服务器和客户端程序,单击客户端“获取时间”按钮,从服务器上获得当前的系统时间,如图12.8所示。
在这里插入图片描述



本章相关例程源码下载

1.Qt5开发及实例_CH1201.rar 下载

Qt5开发及实例_CH1201.rar

2.Qt5开发及实例_CH1202.rar 下载

Qt5开发及实例_CH1202.rar

3.Qt5开发及实例_CH1203.rar 下载

Qt5开发及实例_CH1203.rar

4.Qt5开发及实例_CH1204.rar 下载

Qt5开发及实例_CH1204.rar

5.Qt5开发及实例_CH1205.rar 下载

Qt5开发及实例_CH1205.rar

更多推荐

【Linux】系统编程线程互斥与同步(C++)

目录【1】线程互斥【1.1】进程线程间的互斥相关背景概念【1.2】互斥量mutex【1.3】互斥量实现原理探究【1.4】RAII的加锁风格【2】可重入VS线程安全【2.1】概念【2.2】常见的线程不安全的情况【2.3】常见的线程安全的情况【2.4】常见不可重入的情况【2.5】常见可重入的情况【2.6】可重入与线程安全联

如何从外网远程控制企业内网电脑?

在企业中,保护公司机密和数据安全是至关重要的。为了确保员工在使用公司电脑时遵守相关规定,许多公司会采取外网监控员工电脑的方法。本文将介绍一些真实有效的方法和具体的操作步骤,以帮助您更好地监控员工电脑。一、什么是外网监控?外网监控,又称为远程桌面监控,是指通过网络连接到员工电脑,实时查看员工电脑的操作情况,以便对员工的工

NLP(5)--自编码器

目录一、自编码器1、自编码器概述2、降噪自编码器二、特征分离三、自编码器的其他应用1、文本生成2、图像压缩3、异常检测四、VAE1、极大似然估计2、GSM3、GMM4、VAE的引出5、VAE一、自编码器1、自编码器概述自编码器(Auto-Encoder)作为无监督学习的一种算法,自编码器中包含Encoder和Decod

UEFI 安装 Debian12 Linux 物理机虚拟机VMware通用

文章目录前言⭐前置虚拟机物理机安装流程选择安装方式语言及键盘选择网络选择创建用户系统磁盘分区新旧磁盘分区方式BOOT分区SWAP分区根分区安装过程中其他选项选择软件包安装流程末前言⭐物理机和虚拟机安装仅有设置UFFI引导的差别、这里前置为设置UEFI引导。安装步骤大同小异,理解思路即可轻松完成安装配置。前置虚拟机1.首

Maven的介绍和使用

Maven的作用项目构建依赖管理:避免资源间版本冲突问题统一开发结构:提供统一的项目结构Maven的使用下载完压缩包之后放在合适的目录下,其中apache-maven-3.8.8文件夹是安装的maven,下面的repository是本地仓库,其中要修改setting.xml下的仓库路径,设置阿里云镜像。将这里的路径改为

【C++】AVL树

AVL树1.AVL树的概念2.AVL树的实现2.1节点的定义2.2插入2.3是否是AVL树3.AVL树与红黑树1.AVL树的概念AVL树是一棵二叉搜索树,但它的每个节点的左右子树的高度差的绝对值不超过1,且它的子树也是平衡二叉树。左右子树的高度差也叫平衡因子,平衡因子=右子树叶的高度-左子树的高度。将AVL树与满二叉树

代理IP与Socks5代理:跨界电商智能爬虫的引擎与安全壁垒

摘要:随着跨界电商的蓬勃发展,数据采集和隐私保护成为企业的关键挑战。本文将深入探讨代理IP和Socks5代理在跨界电商中的应用,以及它们在智能爬虫技术中的关键作用,为企业提供数据引擎和安全防护的解决方案。第一部分:背景介绍跨界电商已成为全球贸易的重要组成部分,为企业提供了巨大的市场机会。然而,要在这个竞争激烈的领域中脱

单片机论文参考:4、基于单片机的智能避障小车

第一章引言随着汽车工业的快速发展,关于汽车的研究也越来越受到人们的关注。智能汽车概念的提出给汽车产业带来机遇也带了挑战。汽车的智能化必将是未来汽车产业发展的趋势,在这样的背景下,我们开展了基于超声波和红外线的智能小车的避障研究。超声波作为智能车避障的一种重要手段,以其避障实现方便,计算简单,易于做到实时控制,测量精度也

12:STM32---RTC实时时钟

目录一:时间相关1:Unix时间戳2:UTC/GMT3:时间戳转化二:BKP1:简历2:基本结构三:RTC1:简历2:框图3:RTC基本结构4:RTC操作注意四:案例A:读写备份寄存器1:连接图2:步骤3:代码B:实时时钟1:连接图2:函数介绍3:代码一:时间相关1:Unix时间戳Unix时间戳(UnixTimesta

基于TensorFlow+CNN+协同过滤算法的智能电影推荐系统——深度学习算法应用(含微信小程序、ipynb工程源码)+MovieLens数据集(五)

目录前言总体设计系统整体结构图系统流程图运行环境模块实现1.模型训练1)数据集分析2)数据预处理3)模型创建4)模型训练5)获取特征矩阵2.后端Django1)路由文件2)视图层文件3)项目设置文件相关其它博客工程源代码下载其它资料下载前言本项目专注于MovieLens数据集,并采用TensorFlow中的2D文本卷积

【业务功能篇112】Springboot + Spring Security 权限管理-登录模块开发实战

合家云社区物业管理平台4.权限管理模块研发4.3登录模块开发前台和后台的认证授权统一都使用SpringSecurity安全框架来实现。首次登录过程如下图:4.3.1生成图片校验码4.3.1.1导入工具类(1)导入Constants常量类/***通用常量类*@authorspikeCong*@date2023/5/3**

热文推荐