linux之线程同步

news2024/11/13 19:04:57

1. 互斥锁

定义锁:
pthread_mutex_t  

初始化锁函数:
int pthread_mutex_init(pthread_mutex_t* mutex,pthread_mutexattr_t* attr);       
第一个参数是定义的互斥锁的地址,第二个参数是锁的属性,一般传NULL
互斥锁的属性在创建锁的时候指定,
有四个值可供选择:
* PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
* PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
* PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样保证当不允许多次加锁时不出现最简单情况下的死锁
* PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争
也可以使用pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

加锁函数:
int pthread_mutex_lock(pthread_mutex_t* mutex); //加锁失败就会阻塞或 
int pthread_mutex_trylock(pthread_mutex_t* mutex); //尝试加锁,成功返回0,失败则会返回错误码,不阻塞

解锁函数:
int pthread_mutex_unlock(pthread_mutex_t* mutex); 

销毁锁函数:
int pthread_mutex_destroy(pthread_mutex_t* mutex);

2. 数据库连接池

server

#include "/freecplus/_freecplus.h"
#include "/freecplus/db/oracle/_ooci.h"
/*将socket发来的数据存入数据库*/

pthread_mutex_t mutexs[100];  // 用于数据库连接池的锁。
connection conns[100];  // 数据库连接池。
bool initconns();  // 初始化数据库连接池。
connection* getconn(); // 从连接池中获取一个数据库连接。
void freeconn(connection* in_conn); // 释放数据库连接。
void freeconns(); // 释放数据库连接池。
void* pthmain(void* arg);
CTcpServer TcpServer;   // 创建服务端对象。
std::vector<long> vpthid;    // 存放线程id的容器。
void mainexit(int sig);  // 信号2和15的处理函数。

// 线程清理函数。
void pthmainexit(void* arg);

CLogFile logfile;

int main(int argc, char* argv[])
{
	signal(2, mainexit);  
	signal(15, mainexit);  // 捕获信号2和15

	logfile.Open("/tmp/serverdb.log", "a+");

	if (TcpServer.InitServer(5858) == false) // 初始化TcpServer的通信端口。
	{
		logfile.Write("TcpServer.InitServer(5858) failed.\n"); return -1;
	}


	if (initconns() == false)  // 初始化数据库连接池。
	{
		logfile.Write("initconns() failed.\n"); return -1;
	}

	while (true)
	{
		if (TcpServer.Accept() == false)   // 等待客户端连接。
		{
			logfile.Write("TcpServer.Accept() failed.\n"); return -1;

		}

		logfile.Write("客户端(%s)已连接。\n", TcpServer.GetIP());

		pthread_t pthid;

		if (pthread_create(&pthid, NULL, pthmain, (void*)(long)TcpServer.m_connfd) != 0)
		{
			logfile.Write("pthread_create failed.\n"); return -1;
		}
		vpthid.push_back(pthid);   // 把线程id保存到vpthid容器中。
	}

	return 0;
}

void* pthmain(void* arg)
{
	pthread_cleanup_push(pthmainexit, arg);  // 设置线程清理函数。

	pthread_detach(pthread_self());  // 分离线程。

	pthread_setcanceltype(PTHREAD_CANCEL_DISABLE, NULL);  // 设置取消方式为立即取消。

	int sockfd = (int)(long)arg;  // 与客户端的socket连接。

	int ibuflen = 0;
	char strbuffer[1024];  // 存放数据的缓冲区。

	while (true)
	{
		memset(strbuffer, 0, sizeof(strbuffer));
		if (TcpRead(sockfd, strbuffer, &ibuflen, 300) == false) break; // 接收客户端发过来的请求报文。
		logfile.Write("接收:%s\n", strbuffer);
		connection* conn = getconn();  // 获取一个数据库连接。

		// 处理业务
		sleep(2);
		freeconn(conn);  // 释放一个数据库连接。
		strcat(strbuffer, "ok");      // 在客户端的报文后加上"ok"。

		logfile.Write("发送:%s\n", strbuffer);

		if (TcpWrite(sockfd, strbuffer) == false) break;     // 向客户端回应报文。
	}
	logfile.Write("客户端已断开。\n");    // 程序直接退出,析构函数会释放资源。

	pthread_cleanup_pop(1);
	pthread_exit(0);
}

// 信号2和15的处理函数。
void mainexit(int sig)
{
	logfile.Write("mainexit begin.\n");
	// 关闭监听的socket。
	TcpServer.CloseListen();

	// 取消全部的线程。
	for (int ii = 0; ii < vpthid.size(); ii++)
	{
		logfile.Write("cancel %ld\n", vpthid[ii]);
		pthread_cancel(vpthid[ii]);
	}

	// 释放数据库连接池。
	freeconns();
	logfile.Write("mainexit end.\n");

	exit(0);
}


// 线程清理函数。
void pthmainexit(void* arg)
{
	logfile.Write("pthmainexit begin.\n");
	// 关闭与客户端的socket。
	close((int)(long)arg);

	// 从vpthid中删除本线程的id。
	for (int ii = 0; ii < vpthid.size(); ii++)
	{
		if (vpthid[ii] == pthread_self())

		{

			vpthid.erase(vpthid.begin() + ii);

		}

	}

	logfile.Write("pthmainexit end.\n");
}


// 初始化数据库连接池。
bool initconns()
{
	for (int ii = 0; ii < 10; ii++)
	{
		if (conns[ii].connecttodb("scott/tiger", "Simplified Chinese_China.ZHS16GBK") != 0)
		{
			logfile.Write("connect database failed.\n%s\n", conns[ii].m_cda.message); return false;
		}
	}

	for (int ii = 0; ii < 10; ii++) pthread_mutex_init(&mutexs[ii], NULL);

	return true;
}



// 从连接池中获取一个数据库连接。
connection* getconn()
{
	for (int ii = 0; ii < 10; ii++)
	{
		if (pthread_mutex_trylock(&mutexs[ii]) == 0)
		{
			logfile.Write("get a conn[%d] ok.\n", ii);
			return &conns[ii];
		}

	}
    /*没有获取到连接  新建连接*/
	return NULL;
}


// 释放数据库连接。
void freeconn(connection* in_conn)
{

	for (int ii = 0; ii < 10; ii++)
	{
		if (in_conn == &conns[ii]) pthread_mutex_unlock(&mutexs[ii]);
	}
}



// 释放数据库连接池。
void freeconns()
{
	for (int ii = 0; ii < 10; ii++)
	{
		conns[ii].disconnect(); pthread_mutex_destroy(&mutexs[ii]);
	}
}

client.cpp

#include "_freecplus.h"

int main(int argc, char* argv[])
{
	printf("pid=%d\n", getpid());

	CTcpClient TcpClient;   // 创建客户端的对象。

	if (TcpClient.ConnectToServer("172.21.0.3", 5858) == false) // 向服务端发起连接请求。
	{
		printf("TcpClient.ConnectToServer(\"172.21.0.3\",5858) failed.\n"); return -1;
	}

	char strbuffer[1024];    // 存放数据的缓冲区。

	for (int ii = 0; ii < 50; ii++)   // 利用循环,与服务端进行5次交互。
	{
		memset(strbuffer, 0, sizeof(strbuffer));
		snprintf(strbuffer, 50, "(%d)这是第%d个超级女生,编号%03d。", getpid(), ii + 1, ii + 1);
		printf("发送:%s\n", strbuffer);
		if (TcpClient.Write(strbuffer) == false) break;    // 向服务端发送请求报文。

		memset(strbuffer, 0, sizeof(strbuffer));
		if (TcpClient.Read(strbuffer, 20) == false) break;  // 接收服务端的回应报文。
		printf("接收:%s\n", strbuffer);
		sleep(5);
	}
	// 程序直接退出,析构函数会释放资源。
}

3. 条件变量

3.1 初始化条件变量

初始化条件变量的方式有两种,一种是直接将 PTHREAD_COND_INITIALIZER 赋值给条件变量,例如:

pthread_cond_t myCond = PTHREAD_COND_INITIALIZER;

借助 pthread_cond_init() 函数初始化条件变量,语法格式如下:

int pthread_cond_init(pthread_cond_t * cond, const pthread_condattr_t * attr);

3.2 阻塞当前线程,等待条件成立

int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);
int pthread_cond_timedwait(pthread_cond_t* cond, pthread_mutex_t* mutex, const struct timespec* abstime); 

3.3 解除线程的“阻塞”状态

int pthread_cond_signal(pthread_cond_t* cond);
int pthread_cond_broadcast(pthread_cond_t* cond);

3.4 销毁条件变量

int pthread_cond_destroy(pthread_cond_t *cond);

pthread_cond_wait发生了什么?

  1. 释放了互斥锁
  2. 等待条件
  3. 条件被触发
  4. 给互斥锁被加锁
#include <pthread.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>

pthread_cond_t    myCond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t   mut = PTHREAD_MUTEX_INITIALIZER;

void* th1(void* arg)
{
    while(true)
    {   
        pthread_mutex_lock(&mut);
        pthread_cond_wait(&myCond, &mut);//等待条件
        printf("线程一被唤醒\n");
        pthread_mutex_unlock(&mut);
    }
}

void* th2(void* arg)
{
    while(true)
    {
        pthread_mutex_lock(&mut);
        pthread_cond_wait(&myCond, &mut);
        printf("线程二被唤醒\n");
        pthread_mutex_unlock(&mut);
    }
}

void func(int sig)
{
    pthread_cond_signal(&myCond);
    //pthread_cond_broadcast(&myCond);
}

int main()
{
    signal(15, func);

    pthread_t t1,t2;
    pthread_create(&t1, NULL, th1, NULL);
    pthread_create(&t2, NULL, th2, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_cond_destroy(&myCond);
    pthread_mutex_destroy(&mut);

    return 0;
}

4. 生产者消费者模型

生产消费者模型

// 本程序演示用互斥锁和条件变量实现高速缓存
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <vector>

using namespace std;

int mesgid = 1;  //消息的计数器
// 缓存消息的结构体。
struct st_message
{
	int  mesgid;
	char message[1024];
} stmesg;

std::vector<struct st_message> vcache;  // 用vector容器做缓存

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;      // 声名并初始化条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;   // 声名并初始化互斥锁

// 消费者、出队线程主函数
void* outcache(void* arg)
{
	struct st_message stmesg;

	while (true)
	{		    
		pthread_mutex_lock(&mutex);  // 加锁
		// 如果缓存为空,等待
        // 条件变量虚假唤醒  用if的话会有问题
		while (vcache.size() == 0)
		{
			pthread_cond_wait(&cond, &mutex);
		}
        // 从缓存中获取第一条记录,然后删除该记录
        memcpy(&stmesg, &vcache[0], sizeof(struct st_message)); // 内存拷贝
		vcache.erase(vcache.begin());
		pthread_mutex_unlock(&mutex);  // 解锁
		// 以下是处理业务的代码
		printf("phid=%ld,mesgid=%d\n", pthread_self(), stmesg.mesgid);
		usleep(100);
	}
}

// 生产者、把生产的数据存入缓存
//void* incache(void* arg)
void incache(int arg)
{
    struct st_message stmesg;
    memset(&stmesg, 0, sizeof(struct st_message));
    stmesg.mesgid = mesgid++;
    pthread_mutex_lock(&mutex);  // 加锁。
    // 生产数据,放入缓存。
    vcache.push_back(stmesg);    // 内存拷贝。
    pthread_mutex_unlock(&mutex); // 解锁
    //pthread_cond_broadcast(&cond);  // 触发条件,激活全部的线程。
    pthread_cond_signal(&cond);
}

int main()
{
	signal(15, incache);  //接收15的信号,调用生产者函数。

	pthread_t thid1, thid2, thid3, thid4;
	pthread_create(&thid1, NULL, outcache, NULL);
	pthread_create(&thid2, NULL, outcache, NULL);
	pthread_create(&thid3, NULL, outcache, NULL);
	pthread_join(thid1, NULL);
	pthread_join(thid2, NULL);
	pthread_join(thid3, NULL);

	return 0;
}

注意

  1. 条件触发要写在解锁之后
    pthread_mutex_unlock(&mutex); // 解锁
    pthread_cond_broadcast(&cond);  // 触发条件,激活全部的线程。
  1. 持有锁的时间越短越好

5. 信号量

信号量是一个整数计数器,其数值可以用于标识空闲临界资源的数量。
当有进程释放资源时,信号量增加,表示可用资源数增加;当有进程申请到资源时,信号量减少,表示可用资源数减少;

  • 1、sem_init
    函数声明:int sem_init(sem_t *sem, int pshared, unsigned int value);
    创建信号量,初始化由sem指向的信号对象,设置它的共享选项,并给它一个初始的整数值;pshared控制信号量的类型,如果值为0,就表示这个信号量是当前进程的局部信号量,在当前进程的多个线程之间共享,否则信号量就可以在多个进程之间共享;value为sem的初始值

    返回值:调用成功时返回0,失败返回-1。

  • 2、sem_wait
    函数声明:int sem_wait(sem_t *sem);
    用于以原子操作的方式将信号量的值减1。sem指向的对象是sem_init调用初始化的信号量

    返回值:调用成功返回0,失败返回-1

  • 3、sem_post
    函数声明:int sem_post(sem_t *sem);
    用于以原子操作的方式将信号量的值加1。sem指向的对象是sem_init调用初始化的信号量。
    返回值:调用成功返回0,失败返回-1

  • 4、sem_destroy
    函数声明:int sem_destroy(sem_t *sem);
    用于对用完的信号量的清理。只有用sem_init初始化的信号量才能用sem_destroy销毁
    返回值:调用成功返回0,失败返回-1。

信号量实现生产者消费者模型

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <vector>
#include <semaphore.h>

using namespace std;

int mesgid = 1;  // 消息的记数器。

// 缓存消息的结构体。
struct st_message
{
	int  mesgid;
	char message[1024];
} stmesg;


std::vector<struct st_message> vcache;  // 用vector容器做缓存。

sem_t sem;  // 声明信号量。
pthread_mutex_t mutex;   // 声名并初始化互斥锁。


// 消费者、出队线程主函数。
void* outcache(void* arg)
{
	struct st_message stmesg;

	while (true)
	{
		while (vcache.size() == 0)
		{
			sem_wait(&sem);  // 如果缓存中没有数据,等待信号。
			printf("%ld wait ok.\n", pthread_self());
		}

		pthread_mutex_lock(&mutex);  // 加锁。

		if (vcache.size() == 0) // 判断缓存中是否有数据。
		{
			pthread_mutex_unlock(&mutex);
			continue;  // 解锁,continue
		}

		// 从缓存中获取第一条记录,然后删除该记录。
		memcpy(&stmesg, &vcache[0], sizeof(struct st_message));
		vcache.erase(vcache.begin());
		pthread_mutex_unlock(&mutex);  //解锁

		// 以下是处理业务的代码。
		printf("phid=%ld,mesgid=%d\n", pthread_self(), stmesg.mesgid);
		usleep(100);
	}
}

// 生产者、把生产的数据存入缓存。
void incache(int sig)
{
	struct st_message stmesg;
	memset(&stmesg, 0, sizeof(struct st_message));

	pthread_mutex_lock(&mutex);  // 加锁。

	// 生产数据,放入缓存。
	stmesg.mesgid = mesgid++; vcache.push_back(stmesg);
	stmesg.mesgid = mesgid++; vcache.push_back(stmesg);
	stmesg.mesgid = mesgid++; vcache.push_back(stmesg);
	stmesg.mesgid = mesgid++; vcache.push_back(stmesg);
	stmesg.mesgid = mesgid++; vcache.push_back(stmesg);
	stmesg.mesgid = mesgid++; vcache.push_back(stmesg);

	pthread_mutex_unlock(&mutex); // 解锁。

	sem_post(&sem);  // 信号加1。
	sem_post(&sem);  // 信号加1。
	sem_post(&sem);  // 信号加1。
	sem_post(&sem);  // 信号加1。
	sem_post(&sem);  // 信号加1。
	sem_post(&sem);  // 信号加1。
}

int main()
{
	signal(15, incache);  // 接收15的信号,调用生产者函数。

	sem_init(&sem, 0, 0);  // 初始化信号量。
	pthread_mutex_init(&mutex, NULL); // 初始化互斥锁。

	pthread_t thid1, thid2, thid3;
	pthread_create(&thid1, NULL, outcache, NULL);
	pthread_create(&thid2, NULL, outcache, NULL);
	pthread_create(&thid3, NULL, outcache, NULL);

	pthread_join(thid1, NULL);
	pthread_join(thid2, NULL);
	pthread_join(thid3, NULL);

	return 0;
}

6. 读写锁

互斥锁只有加锁和不加锁两种状态,同一时间只能有一个线程加锁。
读写锁可以有三种状态:读模式下加锁,写模式下加锁和不加锁
读写锁的特点:

  1. 如果某线程申请了读锁,其他线程可以再申请读锁,但不能申请写锁
  2. 如果某线程申请了写锁,其他线程既不能申请读锁,也不能申请写锁
    读写锁适合对数据的读比写次数多的情况。

7. 自旋锁

自旋锁是一种基于忙等待的锁,它在等待锁的过程中不会阻塞线程,而是使用循环不停地检查锁是否可用。如果锁被其他线程占用,当前线程就会一直循环等待,直到锁被释放为止。

自旋锁的优点是等待锁的线程不会被阻塞,因此在锁的占用时间很短的情况下,自旋锁的性能比较高。另外,自旋锁一般比互斥锁的实现更加简单,因为它不需要使用系统调用来挂起和恢复线程。

不过自旋锁也有一些缺点。首先,如果锁的占用时间很长,自旋锁会导致线程一直循环等待,浪费 CPU 资源。其次,自旋锁只适用于单核 CPU 或者多核 CPU 上锁的线程数比较少的情况,因为在多核 CPU 上,如果一个线程一直占用锁,其他线程就会一直循环等待,导致 CPU 资源的浪费和性能下降。

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

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

相关文章

(C语言版)力扣(LeetCode)+牛客网(nowcoder)二叉树基础oj练习

二叉树基础oj练习 965. 单值二叉树题目解法 100. 相同的树题目解法 101. 对称二叉树题目解法 144. 二叉树的前序遍历题目解法 94. 二叉树的中序遍历题目解法 145. 二叉树的后序遍历题目解法 572. 另一棵树的子树题目解法 KY11 二叉树遍历题目解法 结语 965. 单值二叉树 题目 …

Linux安装MongoDB数据库并内网穿透在外远程访问

文章目录 前言1.配置Mongodb源2.安装MongoDB数据库3.局域网连接测试4.安装cpolar内网穿透5.配置公网访问地址6.公网远程连接7.固定连接公网地址8.使用固定公网地址连接 转发自CSDN cpolarlisa的文章&#xff1a;Linux服务器安装部署MongoDB数据库 - 无公网IP远程连接「内网穿透…

LeetCode572. 另一棵树的子树LeetCode

572. 另一棵树的子树 描述示例解题思路以及代码 描述 给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 二叉树 tree 的一棵子树包括 tree 的某个节…

数据库|SQL调优案例之TiFlash帮倒忙该怎么办?

一、背景 早上收到某系统的告警tidb节点挂掉无法访问&#xff0c;情况十万火急。登录中控机查了一下display信息&#xff0c;4个TiDB、Prometheus、Grafana全挂了&#xff0c;某台机器hang死无法连接&#xff0c;经过快速重启后集群恢复&#xff0c;经排查后是昨天上线的某个S…

Flink消费pubsub问题

我看网上flink消费pubsub的资料并不多&#xff0c;最近跑通了&#xff0c;大家有问题的可以给我留言。 一、基本资料 1.flink官网接入方式 Google Cloud PubSub | Apache Flink StreamExecutionEnvironment streamExecEnv StreamExecutionEnvironment.getExecutionEnviron…

Android平台如何实现外部编码后(H.264/H.265)数据实时预览播放

技术背景 我们在对接开发者的时候&#xff0c;遇到这样的诉求&#xff1a;除了正常的RTMP、RTSP直播播放外&#xff0c;有些硬件设备输出编码后&#xff08;H.264/H.265&#xff09;的数据&#xff0c;比如无人机或类似硬件产品&#xff0c;回调出来的H.264/H.265数据&#xf…

C#中的委托是什么

https://www.cnblogs.com/deepalley/p/12150931.html 1.什么是委托&#xff1f;&#xff08;方法作另一个方法的参数&#xff09; delegate void MyDel(int value); //声明委托类型 和类一样&#xff0c;委托是用户自定义的类型&#xff0c;但是类是数据和方法的集合&#…

vue实现功能完整的购物商城,商品零食、电商通用商城

目录 一、项目结构 1.项目截图 2.项目简介 3.项目布局 二、首页 1.效果图 2.源码 三、商品详情 1.效果图 2.源码 四、分类 1.效果图 五、购物车、提交订单 1.效果图 六、个人中心 1.源码结构 2、效果图 七、总结 一、项目结构 1.项目截图 2.项目简介 项目基于vue…

海睿思分享 | 一文读懂企业数据资产目录建设的重要性

小王是某公司信息化部门负责人。 某天&#xff0c;公司领导需要获取近三年来生产部门的人员信息全面数据&#xff0c;小王费了九牛二虎之力&#xff0c;召开了各种会议&#xff0c;在各个系统里来回找数据&#xff0c;最终找到了这些数据。然而领导所需的人员职称、人员获奖信…

UOS服务器系统配置bond

一、Bond介绍 bond可以将多个网卡绑定到一起&#xff0c;可以让两个或多个接口作为一个接口&#xff0c;同时提高带宽&#xff0c;并提供网络链路的冗余&#xff0c;当有其中一块网卡故障的时候&#xff0c;不会中断服务器的业务。 二、Bond模式 1、mode0&#xff08;balanc…

老杨说运维 | 运维数智化转型正确打开方式是什么?他这样说

2023年5月9日&#xff0c;中国计算机用户协会信息科技审计分会会员大会暨金融科技风险管理与审计论坛成功于北京召开。擎创科技CEO杨辰受邀与会&#xff0c;并分享了在数智运维发展过程中对企业数智化转型建设的规划思考以及相关实践经验。 同时&#xff0c;年会上举行了“金融…

《基础知识》提示学习的基本知识

《基础知识》提示学习的基本知识 提示学习背景提示的形式和元素提示学习的输入形式提示学习的重要元素提示学习的输入形式举例基本提示任务提示学习 内容参考:打工人转型之道(二):提示工程(Prompt Engineering)进阶篇

【服务器】利用树莓派搭建 web 服务器【无需公网IP】

Yan-英杰的主页 悟已往之不谏 知来者之可追 C程序员&#xff0c;2024届电子信息研究生 目录 概述 使用 Raspberry Pi Imager 安装 Raspberry Pi OS 设置 Apache Web 服务器 测试 web 站点 安装静态样例站点 将web站点发布到公网 安装 Cpolar内网穿透 cpolar进行tok…

基于 FPGA 的彩色图像灰度化的设计实现(image_stitche_x)

文章目录 前言一、图像合并模块的设计二、仿真文件 前言 image_stitche_x 模块&#xff1a;将串口接收的尺寸为 400480 大小的彩色图像与灰度化处理后的 400480 大小的图像数据以左右形式合并成一张 800*480 的图像。 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面…

AI再度升级,IT业一片哀鸿遍野:程序员真的要失业了吗?

IT人员真的要失业了吗&#xff1f; 随着各个大厂已经相继传来裁员&#xff0c;降薪&#xff0c;减招的消息和ChatGPT等大型AI模型可以定制化写参考代码&#xff0c;甚至通过外接API直接帮助操作&#xff0c;IT人员似乎越来越不吃香了。 其实&#xff0c;ChatGPT有用的不是取代…

Diango学习-用户管理系统(简单部门管理、用户管理)

目录 1、创建项目和app 1.创建项目 2.创建app 2种创建方式 注册app 2、表结构的创建 Django中的模型字段有很多种&#xff0c;包括但不限于&#xff1a; 设计表结构&#xff08;Django&#xff09; 在models.py文件中创建表&#xff1a;部门表和员工表 加入性别列&…

FL Studio21.0.3.3517完整试用版

系统要求 FL STUDIO 可以运行在任何计算机上: 支持 WINDOWS: 7, 8, 10 或者更高版本 支持 MacOS: 10.11 或更高版本 不低于 4GB 的可用硬盘空间 建议最低 4GB 内存或 更高 当然CPU 越强大&#xff0c;也就意味着你运行的音源和效果器越多! FL Studio是一个非常受欢迎的数…

轻松实现远程访问本地wamp服务器,无公网IP也不怕,「内网穿透」

目录 前言 1.Wamp服务器搭建 1.1 Wamp下载和安装 1.2 Wamp网页测试 2. Cpolar内网穿透的安装和注册 2.1 本地网页发布 2.2 Cpolar云端设置 2.3 Cpolar本地设置 3. 公网访问测试 4. 结语 转载自cpolar极点云的文章&#xff1a;无公网IP&#xff1f;教你在外远程访问本地…

新零售发展现状剖析

“新零售”的商业生态将涵盖网络页面、实体店面、支付终端、数据系统、物流平台和营销路径等诸多方面。 企业通过使用大数据和人工智能等先进技术升级产品的生产、流通和销售流程&#xff0c;重塑商业结构与生态圈&#xff0c;深度融合线上服务、线下体验和现代物流。将物流、…

Devexpress GridControl 内部调用外面实现的FocusedRowChanged

个人需求是网格自带的条件发生改变时&#xff08;网格显示的内容会发生改变&#xff09;&#xff0c;同时需要刷新另一个网格的数据源&#xff0c;而另一个网格的数据源是走的这个网格的行焦点改变事件去刷新&#xff0c;自带的条件发生改变时并不会触发行焦点的改变 当前情况…