草庐IT

unique_lock 详解

repinkply 2023-12-02 原文

(1) unique_lock 取代lock_guard

(2)unique_lock的第二个参数

2.1.std::adopt_lock

2.2 std::try_to_lock

2.3 std::defer_lock

(3) unique_lock的成员函数

3.1 lock

3.2 unlock()

3.3 try_lock()

3.4 release()

(4) unique_lock 所有权的传递

unique_lock 取代lock_guard

unique_lock 是一个类模板,工作中,一般使用lock_guard(推荐使用);lock_guard取代了mutex的lock() 和 unlock() 的函数。

unique_lock比lock_guard灵活了很多;效率上差一点,内存上多一点。

// test.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <iostream>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <list>
#include <thread>
#include <chrono>
#include <mutex>

using namespace std;

class A
{
public:
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; i++)
		{
			cout << "inMsgRecvQueue 线程执行,插入一个元素:" << i << endl;

			//lock_guard<mutex> sbguard1(my_mutex1);
			//lock_guard<mutex> sbguard2(my_mutex2);

			unique_lock<mutex> sbguard1(my_mutex1);
			unique_lock<mutex> sbguard2(my_mutex2);

			msgRecvQueue.push_back(i);

		}
	}

	bool outMsgLULProc(int& command)
	{
		//lock_guard<mutex> sbguard1(my_mutex1);
		//lock_guard<mutex> sbguard2(my_mutex2);

		unique_lock<mutex> sbguard1(my_mutex1);
		unique_lock<mutex> sbguard2(my_mutex2);

		if (!msgRecvQueue.empty())
		{
			command = msgRecvQueue.front();
			msgRecvQueue.pop_front();

			return true;
		}

		return false;
	}

	void outMsgRecvQueue()
	{
		int command = 0;
		for (int i = 0; i < 100000; i++)
		{
			bool result = outMsgLULProc(command);
			if (result == true)
			{
				cout << "outMsgRecvQueue(),执行了,从容器中取出一个元素:" << command << endl;
			}
			else
			{
				cout << "outMsgRecvQueue 线程执行了,但是目前消息队列中的元素是为空:" << i << endl;
			}
		}
	}
private:
	list<int> msgRecvQueue;
	mutex my_mutex1;
	mutex my_mutex2;
};

int main()
{
	A myobja;
	thread myInMsgObj(&A::inMsgRecvQueue,&myobja);

	thread myOutMsgObj(&A::outMsgRecvQueue,&myobja);

	myInMsgObj.join();
	myOutMsgObj.join();

	cout << "main 主线程执行结束" << endl;

	return 0;
}

二、lock_guard的第二个参数

adopt_lock标记作用;表示这个互斥量已经被lock了,(你必须要把互斥量提前lock,否者会报异常)

adopt_lock标记的效果就是:假设调用方线程已经拥有了互斥的所有权(已经lock()成功了),通知lock_guard不需要在构造函数中lock这个互斥量了;

unique_lock也可以带std::adopt_lock

my_mutex1.lock()

std::unique_lock<std::mutex> sbguard1(my_mutex1,std::adopt_lock)

// test.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <iostream>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <list>
#include <thread>
#include <chrono>
#include <mutex>

using namespace std;

class A
{
public:
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; i++)
		{
			cout << "inMsgRecvQueue 线程执行,插入一个元素:" << i << endl;

			//lock_guard<mutex> sbguard1(my_mutex1);
			//lock_guard<mutex> sbguard2(my_mutex2);

			my_mutex1.lock();
			unique_lock<mutex> sbguard1(my_mutex1,std::adopt_lock);
			unique_lock<mutex> sbguard2(my_mutex2);

			msgRecvQueue.push_back(i);

		}
	}

	bool outMsgLULProc(int& command)
	{
		//lock_guard<mutex> sbguard1(my_mutex1);
		//lock_guard<mutex> sbguard2(my_mutex2);

		unique_lock<mutex> sbguard1(my_mutex1);
		unique_lock<mutex> sbguard2(my_mutex2);

		if (!msgRecvQueue.empty())
		{
			command = msgRecvQueue.front();
			msgRecvQueue.pop_front();

			return true;
		}

		return false;
	}

	void outMsgRecvQueue()
	{
		int command = 0;
		for (int i = 0; i < 100000; i++)
		{
			bool result = outMsgLULProc(command);
			if (result == true)
			{
				cout << "outMsgRecvQueue(),执行了,从容器中取出一个元素:" << command << endl;
			}
			else
			{
				cout << "outMsgRecvQueue 线程执行了,但是目前消息队列中的元素是为空:" << i << endl;
			}
		}
	}
private:
	list<int> msgRecvQueue;
	mutex my_mutex1;
	mutex my_mutex2;
};

int main()
{
	A myobja;
	thread myInMsgObj(&A::inMsgRecvQueue,&myobja);

	thread myOutMsgObj(&A::outMsgRecvQueue,&myobja);

	myInMsgObj.join();
	myOutMsgObj.join();

	cout << "main 主线程执行结束" << endl;

	return 0;
}

(2.2) std::try_to_lock

// test.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <iostream>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <list>
#include <thread>
#include <chrono>
#include <mutex>

using namespace std;

class A
{
public:
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; i++)
		{
			cout << "inMsgRecvQueue 线程执行,插入一个元素:" << i << endl;

			//lock_guard<mutex> sbguard1(my_mutex1);
			//lock_guard<mutex> sbguard2(my_mutex2);

			unique_lock<mutex> sbguard1(my_mutex1,std::try_to_lock);
			if (sbguard1.owns_lock())
			{
				
			}
			else
			{
				cout << "没有拿到锁,只能做点别的事情:" << i << endl;
			}
			unique_lock<mutex> sbguard2(my_mutex2,std::try_to_lock);
			if (sbguard2.owns_lock())
			{
				msgRecvQueue.push_back(i);
			}
			else
			{
				cout << "没有拿到锁,只能做点别的事情:" << i << endl;
			}
		}
	}

	bool outMsgLULProc(int& command)
	{
		//lock_guard<mutex> sbguard1(my_mutex1);
		//lock_guard<mutex> sbguard2(my_mutex2);

		unique_lock<mutex> sbguard1(my_mutex1);
		unique_lock<mutex> sbguard2(my_mutex2);


		std::chrono::milliseconds dura(20000); //1秒 = 1000毫秒,所以20000毫秒 = 20秒
		std::this_thread::sleep_for(dura);     //休息一定的时长


		if (!msgRecvQueue.empty())
		{
			command = msgRecvQueue.front();
			msgRecvQueue.pop_front();

			return true;
		}

		return false;
	}

	void outMsgRecvQueue()
	{
		int command = 0;
		for (int i = 0; i < 100000; i++)
		{
			bool result = outMsgLULProc(command);
			if (result == true)
			{
				cout << "outMsgRecvQueue(),执行了,从容器中取出一个元素:" << command << endl;
			}
			else
			{
				cout << "outMsgRecvQueue 线程执行了,但是目前消息队列中的元素是为空:" << i << endl;
			}
		}
	}
private:
	list<int> msgRecvQueue;
	mutex my_mutex1;
	mutex my_mutex2;
};

int main()
{
	A myobja;

	thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);

	thread myInMsgObj(&A::inMsgRecvQueue,&myobja);

	myInMsgObj.join();
	myOutMsgObj.join();

	cout << "main 主线程执行结束" << endl;

	return 0;
}

(2.3)std::defer_lock

unique_lock 所支持的另一个参数是:std::defer_lock(用这个defer_lock 的前提是程序员不能自己先去lock 这个mutex,否者会报异常,这点和try_to_lock一样的)

defer_lock的意思就是 并没有给mutex加锁,来介绍一下unique_lock的重要成员函数。

1.unique_lock 的成员函数 lock

// test.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <iostream>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <list>
#include <thread>
#include <chrono>
#include <mutex>

using namespace std;

class A
{
public:
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; i++)
		{
			cout << "inMsgRecvQueue 线程执行,插入一个元素:" << i << endl;

			std::unique_lock<std::mutex> sbguard1(my_mutex1,std::defer_lock);  //没有加锁的my_mutex1
			sbguard1.lock(); //不能自己unlock,unique_lock 会自己unlock
			std::unique_lock<std::mutex> sbguard2(my_mutex2, std::defer_lock); //没有加锁的my_mutex2
			sbguard2.lock();//不能自己unlock,unique_lock 会自己unlock
			
			msgRecvQueue.push_back(i);
			
		}
	}

	bool outMsgLULProc(int& command)
	{
		//lock_guard<mutex> sbguard1(my_mutex1);
		//lock_guard<mutex> sbguard2(my_mutex2);

		unique_lock<mutex> sbguard1(my_mutex1);
		unique_lock<mutex> sbguard2(my_mutex2);


		std::chrono::milliseconds dura(20000); //1秒 = 1000毫秒,所以20000毫秒 = 20秒
		std::this_thread::sleep_for(dura);     //休息一定的时长


		if (!msgRecvQueue.empty())
		{
			command = msgRecvQueue.front();
			msgRecvQueue.pop_front();

			return true;
		}

		return false;
	}

	void outMsgRecvQueue()
	{
		int command = 0;
		for (int i = 0; i < 100000; i++)
		{
			bool result = outMsgLULProc(command);
			if (result == true)
			{
				cout << "outMsgRecvQueue(),执行了,从容器中取出一个元素:" << command << endl;
			}
			else
			{
				cout << "outMsgRecvQueue 线程执行了,但是目前消息队列中的元素是为空:" << i << endl;
			}
		}
	}
private:
	list<int> msgRecvQueue;
	mutex my_mutex1;
	mutex my_mutex2;
};

int main()
{
	A myobja;

	thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);

	thread myInMsgObj(&A::inMsgRecvQueue,&myobja);

	myInMsgObj.join();
	myOutMsgObj.join();

	cout << "main 主线程执行结束" << endl;

	return 0;
}

2.unique_lock 的成员函数 unlock

引入unique_lock 的成员函数 unlock,目的就是:因为有一些非共享代码需要处理,咋们不希望一直被lock住,当共享代码处理完了之后,就立马unlock。

// test.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <iostream>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <list>
#include <thread>
#include <chrono>
#include <mutex>

using namespace std;

class A
{
public:
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; i++)
		{
			cout << "inMsgRecvQueue 线程执行,插入一个元素:" << i << endl;

			std::unique_lock<std::mutex> sbguard1(my_mutex1,std::defer_lock);  //没有加锁的my_mutex1
			sbguard1.lock(); //不能自己unlock,unique_lock 会自己unlock
			std::unique_lock<std::mutex> sbguard2(my_mutex2, std::defer_lock); //没有加锁的my_mutex2
			
			sbguard2.lock();//不能自己unlock,unique_lock 会自己unlock

			//处理一些非共享代码
            //..........
			//.........
			sbguard2.unlock();


			sbguard2.lock();
			
			msgRecvQueue.push_back(i);

            sbguard2.unlock(); //画蛇添足,也可以
			
		}
	}

	bool outMsgLULProc(int& command)
	{
		//lock_guard<mutex> sbguard1(my_mutex1);
		//lock_guard<mutex> sbguard2(my_mutex2);

		unique_lock<mutex> sbguard1(my_mutex1);
		unique_lock<mutex> sbguard2(my_mutex2);


		//std::chrono::milliseconds dura(20000); //1秒 = 1000毫秒,所以20000毫秒 = 20秒
		//std::this_thread::sleep_for(dura);     //休息一定的时长


		if (!msgRecvQueue.empty())
		{
			command = msgRecvQueue.front();
			msgRecvQueue.pop_front();

			return true;
		}

		return false;
	}

	void outMsgRecvQueue()
	{
		int command = 0;
		for (int i = 0; i < 100000; i++)
		{
			bool result = outMsgLULProc(command);
			if (result == true)
			{
				cout << "outMsgRecvQueue(),执行了,从容器中取出一个元素:" << command << endl;
			}
			else
			{
				cout << "outMsgRecvQueue 线程执行了,但是目前消息队列中的元素是为空:" << i << endl;
			}
		}
	}
private:
	list<int> msgRecvQueue;
	mutex my_mutex1;
	mutex my_mutex2;
};

int main()
{
	A myobja;

	thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);

	thread myInMsgObj(&A::inMsgRecvQueue,&myobja);

	myInMsgObj.join();
	myOutMsgObj.join();

	cout << "main 主线程执行结束" << endl;

	return 0;
}

3.3 try_lock(),尝试给互斥量加锁,如果拿不到锁,则返回false,如果拿到了锁,返回true,这个函数是不阻塞的。

// test.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <iostream>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <list>
#include <thread>
#include <chrono>
#include <mutex>

using namespace std;

class A
{
public:
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; i++)
		{
			cout << "inMsgRecvQueue 线程执行,插入一个元素:" << i << endl;

			std::unique_lock<std::mutex> sbguard1(my_mutex1,std::defer_lock);  //没有加锁的my_mutex1
			sbguard1.lock(); //不能自己unlock,unique_lock 会自己unlock

			std::unique_lock<std::mutex> sbguard2(my_mutex2, std::defer_lock); //没有加锁的my_mutex2
			
			if (sbguard2.try_lock() == true)
			{
				msgRecvQueue.push_back(i);
			}
			else
			{
				cout << "inMsgRecvQueue() 线程没有拿到锁,只能干点别的事:" << i << endl;
			}
			
		}
	}

	bool outMsgLULProc(int& command)
	{
		//lock_guard<mutex> sbguard1(my_mutex1);
		//lock_guard<mutex> sbguard2(my_mutex2);

		unique_lock<mutex> sbguard1(my_mutex1);
		unique_lock<mutex> sbguard2(my_mutex2);


		//std::chrono::milliseconds dura(20000); //1秒 = 1000毫秒,所以20000毫秒 = 20秒
		//std::this_thread::sleep_for(dura);     //休息一定的时长


		if (!msgRecvQueue.empty())
		{
			command = msgRecvQueue.front();
			msgRecvQueue.pop_front();

			return true;
		}

		return false;
	}

	void outMsgRecvQueue()
	{
		int command = 0;
		for (int i = 0; i < 100000; i++)
		{
			bool result = outMsgLULProc(command);
			if (result == true)
			{
				cout << "outMsgRecvQueue(),执行了,从容器中取出一个元素:" << command << endl;
			}
			else
			{
				cout << "outMsgRecvQueue 线程执行了,但是目前消息队列中的元素是为空:" << i << endl;
			}
		}
	}
private:
	list<int> msgRecvQueue;
	mutex my_mutex1;
	mutex my_mutex2;
};

int main()
{
	A myobja;

	thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);

	thread myInMsgObj(&A::inMsgRecvQueue,&myobja);

	myInMsgObj.join();
	myOutMsgObj.join();

	cout << "main 主线程执行结束" << endl;

	return 0;
}

4.release()  返回它所管理的mutex对象指针,并释放所有权;也就是说,这个unique_lock 和 mutex不再有关系。

//严格区分unlock()和release()的区别,不要混淆。

//如果原来mutex对象处于加锁状态,你有责任接管过来并负责解锁。(release返回的是原始mutex的指针)

// test.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <iostream>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <list>
#include <thread>
#include <chrono>
#include <mutex>

using namespace std;

class A
{
public:
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; i++)
		{
			cout << "inMsgRecvQueue 线程执行,插入一个元素:" << i << endl;

			std::unique_lock<std::mutex> sbguard1(my_mutex1); 
			std::mutex* ptx = sbguard1.release();

			ptx->unlock();


			std::unique_lock<std::mutex> sbguard2(my_mutex2, std::defer_lock); //没有加锁的my_mutex2
			
			if (sbguard2.try_lock() == true)
			{
				msgRecvQueue.push_back(i);
			}
			else
			{
				cout << "inMsgRecvQueue() 线程没有拿到锁,只能干点别的事:" << i << endl;
			}
			
		}
	}

	bool outMsgLULProc(int& command)
	{
		//lock_guard<mutex> sbguard1(my_mutex1);
		//lock_guard<mutex> sbguard2(my_mutex2);

		unique_lock<mutex> sbguard1(my_mutex1);
		unique_lock<mutex> sbguard2(my_mutex2);


		//std::chrono::milliseconds dura(20000); //1秒 = 1000毫秒,所以20000毫秒 = 20秒
		//std::this_thread::sleep_for(dura);     //休息一定的时长


		if (!msgRecvQueue.empty())
		{
			command = msgRecvQueue.front();
			msgRecvQueue.pop_front();

			return true;
		}

		return false;
	}

	void outMsgRecvQueue()
	{
		int command = 0;
		for (int i = 0; i < 100000; i++)
		{
			bool result = outMsgLULProc(command);
			if (result == true)
			{
				cout << "outMsgRecvQueue(),执行了,从容器中取出一个元素:" << command << endl;
			}
			else
			{
				cout << "outMsgRecvQueue 线程执行了,但是目前消息队列中的元素是为空:" << i << endl;
			}
		}
	}
private:
	list<int> msgRecvQueue;
	mutex my_mutex1;
	mutex my_mutex2;
};

int main()
{
	A myobja;

	thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);

	thread myInMsgObj(&A::inMsgRecvQueue,&myobja);

	myInMsgObj.join();
	myOutMsgObj.join();

	cout << "main 主线程执行结束" << endl;

	return 0;
}

4.unique_lock 所有权的传递(转移)

一般有2种方法

1.std::move(...)

2.return unique_lock对象

有关unique_lock 详解的更多相关文章

  1. ruby-on-rails - Rails、已安装的 Gem 版本和 Gemfile.lock 版本 - 2

    我有一个问题。如果bundle更新,Gemfile.lock总是被提交。今天,当我部署web应用程序时,我看到了这条错误消息,Couldnotfindjwt-1.5.3inanyofthesourcesjwt-1.5.2安装在已部署服务器的共享gem路径中。我认为bundleinstallnewjwt-1.5.3gem因为Gemfile.lock中指定的版本(1.5.3)和安装的版本(1.5.2)不相等。为什么不能bundleinstalljwtgem?如果找不到jwt-1.5.3,是否应该捆绑安装那个gem? 最佳答案 删除Gem

  2. 物联网MQTT协议详解 - 2

    一、什么是MQTT协议MessageQueuingTelemetryTransport:消息队列遥测传输协议。是一种基于客户端-服务端的发布/订阅模式。与HTTP一样,基于TCP/IP协议之上的通讯协议,提供有序、无损、双向连接,由IBM(蓝色巨人)发布。原理:(1)MQTT协议身份和消息格式有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。MQTT传输的消息分为:主题(Topic)和负载(payload)两部分Topic,可以理解为消息的类型,订阅者订阅(Su

  3. Tcl脚本入门笔记详解(一) - 2

    TCL脚本语言简介•TCL(ToolCommandLanguage)是一种解释执行的脚本语言(ScriptingLanguage),它提供了通用的编程能力:支持变量、过程和控制结构;同时TCL还拥有一个功能强大的固有的核心命令集。TCL经常被用于快速原型开发,脚本编程,GUI和测试等方面。•实际上包含了两个部分:一个语言和一个库。首先,Tcl是一种简单的脚本语言,主要使用于发布命令给一些互交程序如文本编辑器、调试器和shell。由于TCL的解释器是用C\C++语言的过程库实现的,因此在某种意义上我们又可以把TCL看作C库,这个库中有丰富的用于扩展TCL命令的C\C++过程和函数,所以,Tcl是

  4. ruby-on-rails - Rails add_index 算法 : :concurrently still causes database lock up during migration - 2

    为了防止在迁移到生产站点期间出现数据库事务错误,我们遵循了https://github.com/LendingHome/zero_downtime_migrations中列出的建议。(具体由https://robots.thoughtbot.com/how-to-create-postgres-indexes-concurrently-in概述),但在特别大的表上创建索引期间,即使是索引创建的“并发”方法也会锁定表并导致该表上的任何ActiveRecord创建或更新导致各自的事务失败有PG::InFailedSqlTransaction异常。下面是我们运行Rails4.2(使用Acti

  5. 【详解】Docker安装Elasticsearch7.16.1集群 - 2

    开门见山|拉取镜像dockerpullelasticsearch:7.16.1|配置存放的目录#存放配置文件的文件夹mkdir-p/opt/docker/elasticsearch/node-1/config#存放数据的文件夹mkdir-p/opt/docker/elasticsearch/node-1/data#存放运行日志的文件夹mkdir-p/opt/docker/elasticsearch/node-1/log#存放IK分词插件的文件夹mkdir-p/opt/docker/elasticsearch/node-1/plugins若你使用了moba,直接右键新建即可如上图所示依次类推创建

  6. 【Elasticsearch基础】Elasticsearch索引、文档以及映射操作详解 - 2

    文章目录概念索引相关操作创建索引更新副本查看索引删除索引索引的打开与关闭收缩索引索引别名查询索引别名文档相关操作新建文档查询文档更新文档删除文档映射相关操作查询文档映射创建静态映射创建索引并添加映射概念es中有三个概念要清楚,分别为索引、映射和文档(不用死记硬背,大概有个印象就可以)索引可理解为MySQL数据库;映射可理解为MySQL的表结构;文档可理解为MySQL表中的每行数据静态映射和动态映射上面已经介绍了,映射可理解为MySQL的表结构,在MySQL中,向表中插入数据是需要先创建表结构的;但在es中不必这样,可以直接插入文档,es可以根据插入的文档(数据),动态的创建映射(表结构),这就

  7. 最强Http缓存策略之强缓存和协商缓存的详解与应用实例 - 2

    HTTP缓存是指浏览器或者代理服务器将已经请求过的资源保存到本地,以便下次请求时能够直接从缓存中获取资源,从而减少网络请求次数,提高网页的加载速度和用户体验。缓存分为强缓存和协商缓存两种模式。一.强缓存强缓存是指浏览器直接从本地缓存中获取资源,而不需要向web服务器发出网络请求。这是因为浏览器在第一次请求资源时,服务器会在响应头中添加相关缓存的响应头,以表明该资源的缓存策略。常见的强缓存响应头如下所述:Cache-ControlCache-Control响应头是用于控制强制缓存和协商缓存的缓存策略。该响应头中的指令如下:max-age:指定该资源在本地缓存的最长有效时间,以秒为单位。例如:Ca

  8. IDEA 2022 创建 Spring Boot 项目详解 - 2

    如何用IDEA2022创建并初始化一个SpringBoot项目?目录如何用IDEA2022创建并初始化一个SpringBoot项目?0. 环境说明1.  创建SpringBoot项目 2.编写初始化代码0. 环境说明IDEA2022.3.1JDK1.8SpringBoot1.  创建SpringBoot项目        打开IDEA,选择NewProject创建项目。        填写项目名称、项目构建方式、jdk版本,按需要修改项目文件路径等信息。        选择springboot版本以及需要的包,此处只选择了springweb。        此处需特别注意,若你使用的是jdk1

  9. ruby - Cucumber 测试无法启动,错误为 "Display socket is taken but lock file is missing.." - 2

    运行cucumber后bundleexeccucumberfeatures/emails.feature:20我遇到了错误Displaysocketistakenbutlockfileismissing-checktheHeadlesstroubleshootingguide(Headless::Exception)/Users/me/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/headless-2.2.0/lib/headless.rb:195:inensure_xvfb_is_running'/Users/me/.rbenv/ver

  10. 详解Unity中的粒子系统Particle System (二) - 2

    前言上一篇我们简要讲述了粒子系统是什么,如何添加,以及基本模块的介绍,以及对于曲线和颜色编辑器的讲解。从本篇开始,我们将按照模块结构讲解下去,本篇主要讲粒子系统的主模块,该模块主要是控制粒子的初始状态和全局属性的,以下是关于该模块的介绍,请大家指正。目录前言本系列提要一、粒子系统主模块1.阅读前注意事项2.参考图3.参数讲解DurationLoopingPrewarmStartDelayStartLifetimeStartSpeed3DStartSizeStartSize3DStartRotationStartRotationFlipRotationStartColorGravityModif

随机推荐