进程,线程,应用程序的定义网上有很多资料,但是有些抽象。通俗的来讲,进程就是 一旦一个应用程序开始运行,那么这个应用程序就会存在一个属于这个应用程序的进程。线程就是进程中的基本执行单元,每个进程中都至少存在着一个线程,这个线程是根据进程创建而创建的,所以这个线程我们称之为主线程。那么多线程就是包含有除了主线程之外的其他线程。如果一个线程可以执行一个任务,那么多线程就是可以同时执行多个任务。
Thread 类,Thread 类是用于控制线程的基础类,它存在于 System.Threading 命名空间。通过 Thread 可以控制当前应用程序域中线程的创建、挂起、停止、销毁。
Thread 一些常用属性:
Thread 一些常用属性
Thread 一些常用方法:
Thread 一些常用方法
Thread 的优先级:
Thread 的优先级
创建一个控制台应用程序,
namespace MultiThreadDemo
{
class Program
{
static void Main(string[] args)
{
ThreadDemoClass demoClass = new ThreadDemoClass();
/////如下两种方法都可以创建线程,二选一即可
/////////////////创建线程 方法1 S
////通过类方法直接创建线程
//Thread thread = new Thread(demoClass.Run);
/////////////////创建线程 方法1 E
///////////////创建线程 方法2 S
//创建一个委托,并把要执行的方法作为参数传递给这个委托
ThreadStart threadStart = new ThreadStart(demoClass.Run);
Thread thread = new Thread(threadStart);
///////////////创建线程 方法2 E
//设置为后台线程
thread.IsBackground = true;
//开始线程
thread.Start();
////等待直到线程完成
//thread.Join();
Console.WriteLine("Main thread working...");
Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
Console.ReadKey();
}
}
public class ThreadDemoClass
{
public void Run()
{
Console.WriteLine("Child thread working...");
Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
}
}
}
运行结果:
说明:
1)主线程创建了一个子线程并启动了它,但是主线程没有等到子线程执行完成,而是继续再往下执行的------线程异步或同步
2)如果要等子线程执行完成后再执行主线程-----通过线程的join()方法,方法不难,不单独展开
3)上面创建进程的方法没有带参数和返回值------参考3.2节
4)thread.IsBackground = true,即把当前线程设置为后台线程,因为使用 thread.Start() 启动的线程默认为前台线程。区别:前台线程就是系统会等待所有的前台线程运行结束后,应用程序域才会自动卸载。而设置为后台线程之后,应用程序域会在主线程执行完成时被卸载,而不会等待异步线程的执行完成。
上面的这种使用多线程的方式只是简单的输出一段内容而已,多数情况下我们需要对线程调用的方法传入参数和接收返回值的,但是上面这种方法是不接受参数并且没有返回值的,那么我们可以使用 ParameterizedThreadStart 委托来创建多线程,这个委托可以接受一个 object 类型的参数,我们可以在这上面做文章,看如下示例,
参考如下示例代码,
namespace MultiThreadDemo
{
class Program
{
static void Main(string[] args)
{
ThreadParameterDemoClass demoClass = new ThreadParameterDemoClass();
//创建一个委托,并把要执行的方法作为参数传递给这个委托
ParameterizedThreadStart threadStart = new ParameterizedThreadStart(demoClass.Run);
//创建一个新的线程
Thread thread = new Thread(threadStart);
//开始线程,并传入参数
thread.Start("shufac");
Console.WriteLine("Main thread working...");
Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
Console.ReadKey();
}
}
public class ThreadParameterDemoClass
{
public void Run(object obj)
{
string name = obj as string;
Console.WriteLine("Child thread working...");
Console.WriteLine("My name is " + name);
Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
}
}
}
运行结果:
从上面的运行结果可以看到在多线程实现了参数的传递,可是它也只有一个参数,并且它接受的参数是 object 类型的(万类之源),也就是说既可以是值类型或引用类型,也可以是自定义类型。(当然,自定义类型其实也是属于引用类型的)下面我们使用自定义类型作为参数传递。
参考如下示例代码:
namespace MultiThreadDemo
{
class Program
{
static void Main(string[] args)
{
ThreadSelfDefParameterDemoClass demoClass = new ThreadSelfDefParameterDemoClass();
//创建一个委托,并把要执行的方法作为参数传递给这个委托
ParameterizedThreadStart threadStart = new ParameterizedThreadStart(demoClass.Run);
//创建一个新的线程
Thread thread = new Thread(threadStart);
UserInfo userInfo = new UserInfo();
userInfo.Name = "shufac";
userInfo.Age = 30;
//开始线程,并传入参数
thread.Start(userInfo);
Console.WriteLine("Main thread working...");
Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
Console.ReadKey();
}
}
public class ThreadSelfDefParameterDemoClass
{
public void Run(object obj)
{
UserInfo userInfo = (UserInfo)obj;
Console.WriteLine("Child thread working...");
Console.WriteLine("My name is " + userInfo.Name);
Console.WriteLine("I'm " + userInfo.Age + " years old this year");
Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
}
}
public class UserInfo
{
public string Name { get; set; }
public int Age { get; set; }
}
}
运行结果:
可以使用成员变量来试试 获取线程的返回值,参考如下示例代码,
class Program
{
List<UserInfo> userInfoList = new List<UserInfo>();
static void Main(string[] args)
{
Program program = new Program();
ParameterizedThreadStart threadStart = new ParameterizedThreadStart(program.Run);
Thread thread = null;
UserInfo userInfo = null;
for (int i = 0; i < 3; i++)
{
userInfo = new UserInfo();
userInfo.Name = "Brambling" + i.ToString();
userInfo.Age = 33 + i;
thread = new Thread(threadStart);
thread.Start(userInfo);
thread.Join();
}
foreach (UserInfo user in program.userInfoList)
{
Console.WriteLine("My name is " + user.Name);
Console.WriteLine("I'm " + user.Age + " years old this year");
Console.WriteLine("Thread ID is:" + user.ThreadId);
}
Console.ReadKey();
}
public void Run(object obj)
{
UserInfo userInfo = (UserInfo)obj;
userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId;
userInfoList.Add(userInfo);
}
}
运行结果:
用上面这种方法勉强可以满足返回值的需求,但是却有很大的局限性,因为这里我使用的是成员变量,所以也就限制了线程调用的方法必须是在同一个类里面。
所以也就有了下面的方法,使用委托异步调用的方法(参考第3.4节------异步委托)。
小结:
1)传一个参数的方法,任何内置类型的数据应该都是可以的
2)使用自定义类型作为参数传递,理论上更多个参数也都是可以实现的
3)使用 ThreadStart 和 ParameterizedThreadStart 创建线程还是比较简单的,但是由于线程的创建和销毁需要耗费一定的开销,过多地使用线程反而会造成内存资源的浪费,从而影响性能,出于对性能的考虑,于是引入了线程池的概念。
线程池并不是在 CLR 初始化的时候立刻创建线程的,而是在应用程序要创建线程来执行任务的时候,线程池才会初始化一个线程,初始化的线程和其他线程一样,但是在线程完成任务之后不会自行销毁,而是以挂起的状态回到线程池。当应用程序再次向线程池发出请求的时候,线程池里挂起的线程会再度激活执行任务。这样做可以减少线程创建和销毁所带来的开销。线程池建立的线程默认为后台线程。
示例代码:
namespace MultiThreadDemo
{
class Program
{
static void Main(string[] args)
{
ThreadPoolDemoClass demoClass = new ThreadPoolDemoClass();
//设置当没有请求时线程池维护的空闲线程数
//第一个参数为辅助线程数
//第二个参数为异步 I/O 线程数
ThreadPool.SetMinThreads(5, 5);
//设置同时处于活动状态的线程池的线程数,所有大于次数目的请求将保持排队状态,直到线程池变为可用
//第一个参数为辅助线程数
//第二个参数为异步 I/O 线程数
ThreadPool.SetMaxThreads(100, 100);
//使用委托绑定线程池要执行的方法(无参数)
WaitCallback waitCallback1 = new WaitCallback(demoClass.Run1);
//将方法排入队列,在线程池变为可用时执行
ThreadPool.QueueUserWorkItem(waitCallback1);
//使用委托绑定线程池要执行的方法(有参数)
WaitCallback waitCallback2 = new WaitCallback(demoClass.Run1);
//将方法排入队列,在线程池变为可用时执行
ThreadPool.QueueUserWorkItem(waitCallback2, "Brambling");
UserInfo userInfo = new UserInfo();
userInfo.Name = "Brambling";
userInfo.Age = 33;
//使用委托绑定线程池要执行的方法(有参数,自定义类型的参数)
WaitCallback waitCallback3 = new WaitCallback(demoClass.Run2);
//将方法排入队列,在线程池变为可用时执行
ThreadPool.QueueUserWorkItem(waitCallback3, userInfo);
Console.WriteLine();
Console.WriteLine("Main thread working...");
Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
Console.ReadKey();
}
}
public class ThreadPoolDemoClass
{
public void Run1(object obj)
{
string name = obj as string;
Console.WriteLine();
Console.WriteLine("Child thread working...");
Console.WriteLine("My name is " + name);
Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
}
public void Run2(object obj)
{
UserInfo userInfo = (UserInfo)obj;
Console.WriteLine();
Console.WriteLine("Child thread working...");
Console.WriteLine("My name is " + userInfo.Name);
Console.WriteLine("I'm " + userInfo.Age + " years old this year");
Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
}
}
public class UserInfo
{
public string Name { get; set; }
public int Age { get; set; }
}
}
运行结果:
使用线程池建立的线程也可以选择传递参数或不传递参数,并且参数也可以是值类型或引用类型(包括自定义类型)。看上面的结果发现了什么?没错,第一次执行的方法的线程ID为4,最后一次执行的方法的线程ID也为4。这就说明第一次请求线程池的时候,线程池建立了一个线程,当它执行完成之后就以挂起状态回到了线程池,在最后一次请求的时候,再次唤醒了该线程执行任务。这样就很容易理解了。
小结:线程池的引入主要解决频繁创建线程造成的内存资源的浪费,是个辅助功能,特殊的应用场景比较有用,用于提升性能。
委托的异步调用有两个比较重要的方法:BeginInvoke() 和 EndInvoke()。
参考如下示例代码:
class Program
{
//定义一个委托类
private delegate UserInfo MyDelegate(UserInfo userInfo);
static void Main(string[] args)
{
ThreadDemoClass demoClass = new ThreadDemoClass();
List<UserInfo> userInfoList = new List<UserInfo>();
UserInfo userInfo = null;
UserInfo userInfoRes = null;
//创建一个委托并绑定方法
MyDelegate myDelegate = new MyDelegate(demoClass.Run);
for (int i = 0; i < 3; i++)
{
userInfo = new UserInfo();
userInfo.Name = "Brambling" + i.ToString();
userInfo.Age = 33 + i;
//传入参数并执行异步委托
IAsyncResult result = myDelegate.BeginInvoke(userInfo,null,null);
//异步操作是否完成
while (!result.IsCompleted)
{
Thread.Sleep(100);
Console.WriteLine("Main thread working...");
Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
Console.WriteLine();
}
//结束异步委托,并获取返回值
userInfoRes = myDelegate.EndInvoke(result);
userInfoList.Add(userInfoRes);
}
foreach (UserInfo user in userInfoList)
{
Console.WriteLine("My name is " + user.Name);
Console.WriteLine("I'm " + user.Age + " years old this year");
Console.WriteLine("Thread ID is:" + user.ThreadId);
}
Console.ReadKey();
}
}
public class ThreadDemoClass
{
public UserInfo Run(UserInfo userInfo)
{
userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Child thread working...");
Console.WriteLine("Child thread ID is:" + userInfo.ThreadId);
Console.WriteLine();
return userInfo;
}
}
执行结果:
BeginInvoke() 方法用于异步委托的执行开始,EndInvoke() 方法用于结束异步委托,并获取异步委托执行完成后的返回值。IAsyncResult.IsCompleted 用于监视异步委托的执行状态(true / false),这里的时间是不定的,也就是说一定要等到异步委托执行完成之后,这个属性才会返回 true。如果异步委托的方法耗时较长,那么主线程会一直工作下去。BeginInvoke() 是可以接受多个参数的,它的参数个数和参数类型取决于定义委托时的参数个数和类型,无论它有多少个参数,最后两个参数都是不变的,下面我们会说到。
我们还可以用下面的方法 WaitOne(),自定义一个等待的时间,如果在这个等待时间内异步委托没有执行完成,那么就会执行 while 里面的主线程的逻辑,反之就不会执行。
class Program
{
//定义一个委托类
private delegate UserInfo MyDelegate(UserInfo userInfo);
static void Main(string[] args)
{
ThreadDemoClass demoClass = new ThreadDemoClass();
List<UserInfo> userInfoList = new List<UserInfo>();
UserInfo userInfo = null;
UserInfo userInfoRes = null;
//创建一个委托并绑定方法
MyDelegate myDelegate = new MyDelegate(demoClass.Run);
for (int i = 0; i < 3; i++)
{
userInfo = new UserInfo();
userInfo.Name = "Brambling" + i.ToString();
userInfo.Age = 33 + i;
//传入参数并执行异步委托
IAsyncResult result = myDelegate.BeginInvoke(userInfo,null,null);
//阻止当前线程,直到 WaitHandle 收到信号,参数为指定等待的毫秒数
while (!result.AsyncWaitHandle.WaitOne(1000))
{
Console.WriteLine("Main thread working...");
Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
Console.WriteLine();
}
//结束异步委托,并获取返回值
userInfoRes = myDelegate.EndInvoke(result);
userInfoList.Add(userInfoRes);
}
foreach (UserInfo user in userInfoList)
{
Console.WriteLine("My name is " + user.Name);
Console.WriteLine("I'm " + user.Age + " years old this year");
Console.WriteLine("Thread ID is:" + user.ThreadId);
}
Console.ReadKey();
}
}
public class ThreadDemoClass
{
public UserInfo Run(UserInfo userInfo)
{
userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Child thread working...");
Console.WriteLine("Child thread ID is:" + userInfo.ThreadId);
Console.WriteLine();
return userInfo;
}
}
运行结果:
WaitOne() 方法只能用于监视当前线程的对象,如果要监视多个对象可以使用 WaitAny(WaitHandle[], int)或 WaitAll (WaitHandle[] , int) 这两个方法。
WaitOne() 方法只能用于监视当前线程的对象,如果要监视多个对象可以使用 WaitAny(WaitHandle[], int)或 WaitAll (WaitHandle[] , int) 这两个方法。
参考如下示例代码,
class Program
{
//定义一个委托类
private delegate UserInfo MyDelegate(UserInfo userInfo);
static void Main(string[] args)
{
ThreadDemoClass demoClass = new ThreadDemoClass();
List<UserInfo> userInfoList = new List<UserInfo>();
UserInfo userInfo = null;
UserInfo userInfoRes = null;
//创建一个委托并绑定方法
MyDelegate myDelegate = new MyDelegate(demoClass.Run);
for (int i = 0; i < 3; i++)
{
userInfo = new UserInfo();
userInfo.Name = "Brambling" + i.ToString();
userInfo.Age = 33 + i;
//传入参数并执行异步委托
IAsyncResult result = myDelegate.BeginInvoke(userInfo,null,null);
IAsyncResult result1 = myDelegate.BeginInvoke(userInfo, null, null);
//定义要监视的对象,不能包含对同一对象的多个引用
WaitHandle[] waitHandles = new WaitHandle[] { result.AsyncWaitHandle, result1.AsyncWaitHandle };
while (!WaitHandle.WaitAll(waitHandles,1000))
{
Console.WriteLine("Main thread working...");
Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
Console.WriteLine();
}
//结束异步委托,并获取返回值
userInfoRes = myDelegate.EndInvoke(result);
userInfoList.Add(userInfoRes);
userInfoRes = myDelegate.EndInvoke(result1);
userInfoList.Add(userInfoRes);
}
foreach (UserInfo user in userInfoList)
{
Console.WriteLine("My name is " + user.Name);
Console.WriteLine("I'm " + user.Age + " years old this year");
Console.WriteLine("Thread ID is:" + user.ThreadId);
}
Console.ReadKey();
}
}
public class ThreadDemoClass
{
public UserInfo Run(UserInfo userInfo)
{
userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Child thread working...");
Console.WriteLine("Child thread ID is:" + userInfo.ThreadId);
Console.WriteLine();
return userInfo;
}
}
运行结果:
WaitAll() 方法和 WaitAny() 方法都可以监视多个对象,不同的是 WaitAll() 方法需要等待所有的监视对象都收到信号之后才会返回 true,否则返回 false。而 WaitAny() 则是当有一个监视对象收到信号之后就会返回一个 int 值,这个 int 值代表的是当前收到信号的监视对象的索引。注意:在定义监视对象的时候,不能包含对同一个对象的多个引用,我这里是定义的两个示例,所以是不同的对象。
接下来我们看上面的执行结果。为什么主线程没有“工作”呢?这里你可以在异步委托调用的 Run() 方法里面为线程设置一个几秒钟或者更长地挂起时间。然后在设置监视对象这里设置一个小于线程挂起的时间,然后调试你就能发现问题的所在了。其实也不算是问题,只是之前的理解有误。
其实 WaitAll() 方法和 WaitAny() 方法设置监视对象,然后指定一个时间(毫秒值),这里的意思是当所有的监视对象在指定的时间内都接收到信号时(这里是指 WaitAll() 方法),就不会执行 while 里面的主线程的工作,反之就会执行。
这里你可能会有疑问,如果是这样,那我把前面的逻辑非运算符去掉那不就相反了么。这么理解逻辑上是没错的,但是我还是要说的是,尽量不要去尝试,因为这会是个死循环。WaitAny() 这个方法也是一样的理解,不同的是,它不需要等到所有的监视对象都收到信号,它只需要一个监视对象收到信号就够了,这里就不在演示了。
上面的方法可以看出,我们虽然使用的是异步的方式调用的方法,但是依旧需要等待异步的方法返回执行的结果,尽管我们可以不阻塞主线程,但是还是觉得不太方便。所以也就有了本篇博客最后一个要点,异步委托的回调函数。
示例代码如下,
class Program
{
//定义一个委托类
private delegate UserInfo MyDelegate(UserInfo userInfo);static void Main(string[] args)
{
ThreadDemoClass demoClass = new ThreadDemoClass();
UserInfo userInfo = null;
//创建一个委托并绑定方法
MyDelegate myDelegate = new MyDelegate(demoClass.Run);
//创建一个回调函数的委托
AsyncCallback asyncCallback = new AsyncCallback(Complete);
for (int i = 0; i < 3; i++)
{
userInfo = new UserInfo();
userInfo.Name = "Brambling" + i.ToString();
userInfo.Age = 33 + i;
//传入参数并执行异步委托,并设置回调函数
IAsyncResult result = myDelegate.BeginInvoke(userInfo, asyncCallback, null);
}
Console.WriteLine("Main thread working...");
Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
Console.WriteLine();
Console.ReadKey();
}
public static void Complete(IAsyncResult result)
{
UserInfo userInfoRes = null;
AsyncResult asyncResult = (AsyncResult)result;
//获取在其上调用异步调用的委托对象
MyDelegate myDelegate = (MyDelegate)asyncResult.AsyncDelegate;
//结束在其上调用的异步委托,并获取返回值
userInfoRes = myDelegate.EndInvoke(result);
Console.WriteLine("My name is " + userInfoRes.Name);
Console.WriteLine("I'm " + userInfoRes.Age + " years old this year");
Console.WriteLine("Thread ID is:" + userInfoRes.ThreadId);
}
}
public class ThreadDemoClass
{
public UserInfo Run(UserInfo userInfo)
{
userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Child thread working...");
Console.WriteLine("Child thread ID is:" + userInfo.ThreadId);
Console.WriteLine();
return userInfo;
}
}
运行结果:
从上面可以看到主线程再执行了异步委托之后继续执行了下去,然后在回调函数里输出了信息,也就是说在调用了异步委托之后就不管了,把之后的结束委托和获取委托的返回值放到了回调函数中,因为回调函数是没有返回值的,但是回调函数可以有一个参数。上面说到的 BeginInvoke() 方法的最后两个参数,它的倒数第二个参数就是一个回调函数的委托,最后一个参数可以设置传入回调函数的参数。如下:
class Program
{
//定义一个委托类
private delegate UserInfo MyDelegate(UserInfo userInfo);
static List<UserInfo> userInfoList = new List<UserInfo>();
static void Main(string[] args)
{
ThreadDemoClass demoClass = new ThreadDemoClass();
UserInfo userInfo = null;
//创建一个委托并绑定方法
MyDelegate myDelegate = new MyDelegate(demoClass.Run);
//创建一个回调函数的委托
AsyncCallback asyncCallback = new AsyncCallback(Complete);
//回调函数的参数
string str = "I'm the parameter of the callback function!";
for (int i = 0; i < 3; i++)
{
userInfo = new UserInfo();
userInfo.Name = "Brambling" + i.ToString();
userInfo.Age = 33 + i;
//传入参数并执行异步委托,并设置回调函数
IAsyncResult result = myDelegate.BeginInvoke(userInfo, asyncCallback, str);
}
Console.WriteLine("Main thread working...");
Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
Console.WriteLine();
Console.ReadKey();
}
public static void Complete(IAsyncResult result)
{
UserInfo userInfoRes = null;
AsyncResult asyncResult = (AsyncResult)result;
//获取在其上调用异步调用的委托对象
MyDelegate myDelegate = (MyDelegate)asyncResult.AsyncDelegate;
//结束在其上调用的异步委托,并获取返回值
userInfoRes = myDelegate.EndInvoke(result);
Console.WriteLine("My name is " + userInfoRes.Name);
Console.WriteLine("I'm " + userInfoRes.Age + " years old this year");
Console.WriteLine("Thread ID is:" + userInfoRes.ThreadId);
//获取回调函数的参数
string str = result.AsyncState as string;
Console.WriteLine(str);
}
}
public class ThreadDemoClass
{
public UserInfo Run(UserInfo userInfo)
{
userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Child thread working...");
Console.WriteLine("Child thread ID is:" + userInfo.ThreadId);
Console.WriteLine();
return userInfo;
}
}
运行结果:
回调函数的参数也是 object 类型的,我这里用的是一个 string 类型,但是它也可以是自定义类型的参数。
本篇博客到此结束,在写这篇博客的同时也让我个人对多线程编程加深了理解,多线程编程的知识点还有很多,后面再继续与大家分享。
参考:
1)http://www.cnblogs.com/leslies2/archive/2012/02/07/2310495.html#t1
2)https://www.cnblogs.com/Brambling/p/7144015.html
言:
在python里面,数据可视化是python的一个亮点。在python里面,数据可视可以达到什么样的效果,这当然与我们使用的库有关。python常常需要导入库,并不断调用方法,就很像一条流数据可视化的库,有很多,很多都可以后续开发,然后我们调用。了解过pyecharts美观的可视化界面 ,将pyecharts和matplotlib相对比一下。
pyecharts和matplotlib的区别在哪里呢?Matplotlib是Python数据可视化库的泰斗,尽管已有十多年的历史,但仍然是Python社区中使用最广泛的绘图库,它的设计与MATLAB非常相似,提供了一整套和MATLAB相似的命令API,适合交互式制图,还可以将它作为绘图控件,嵌入其它应用程序中。
Pyecharts是一款将Python与Echarts相结合的数据可视化工具,可以高度灵活的配置,轻松搭配出精美的视图。其中Echarts是百度开源的一个数据可视化库,而Pyecharts将Echarts与Python进行有机对接,方便在Python中直接生成各种美观的图形。
数据可视化之matplotlib绘制正余弦曲线图
我们先来看最终的实现效果
上面这个图是最终保存的图片查看效果
我们一步一步来实现
1:首先我们需要导入基本的库
matplotlib numpy
matplotlib 是我们本章需要的库
numpy 是我们数据分析处理的常见库,在机器学习时也会经常用到。
一步一步来了
下面展示一些 内联代码片。
第一步
#1:用到的方法及参数
# linspace(start, stop, num=50, endpoint=True,
# retstep=False, dtype=None)
# 相关参数的说明
# 指定在start到stop均分数值
# start:不可省略
# stop:有时包含有时不包含,根据endpoint来选择,默认包含
# num:指定均分的数量,默认为50
# endpoint:布尔值,可选,默认为True。包含stop就True,不包含就# False
# retstep : 布尔值,可选,默认为False。如果为True,返回值和步长
# dtype : 输出数据类型,可选。如果不指定,则根据前面参数的数据类型
# 2:np.plot(x,y.color.lw,label,ls,mk,mec,mfc)
# 其中X由所有输入点坐标的x值组成,Y是由与X中包含的x对应的y所组
# 成的向量
# color 指定线条的颜色
# lw指定线条的宽度
# lw指定线条的样式,默认为实现
# mk可以指定数据标识符
# ms可以指定标识符的大小
# mec可以指定标识符的边界色彩
# mfc标识符内部填充颜色
import matplotlib.pyplot as plt
import numpy as np
#用于正常显示中文标签
plt.rcParams['font.sans-serif']=['SimHei']
# 用于正常显示符号
plt.rcParams['axes.unicode_minus']=False
x = np.linspace(-np.pi,np.pi,256,endpoint = 256)
sin,cos = np.sin(x),np.cos(x)
#绘制,并设置相关的参数,这里标签还不会显示出来,因为还没有
#添加图例,具体往下面看
plt.plot(x,sin,color = 'blue',lw=2.5,label = '正弦sin',mec='red')
plt.plot(x,cos,color = 'red',lw = 2.5,label = '余弦cos()')
plt.show() #显示
第二步
#用到的方法及参数
# plt.xlim(xmin, xmax)
# xmin:x轴上的最小值
# xmax:x轴上的最大值
#plt.ylim() 与上一样的道理
#具体如何使用,可以看下面的实例代码
plt.xlim(x.min()*1.5,x.max()*1.5) #将x轴拉伸1.5倍
plt.ylim(cos.min()*1.5,cos.max()*1.5) # 将y轴拉伸1.5倍
plt.show()
第三步
#用到的方法与参数
# plt.xticks([],[])
# plt.yticks([],[])
# plt.title('',color,color,..) #设置标题,里面的相关参数可以# 指定
# plt.text(备注内容x轴的坐标,备注内容y轴的坐标 ,'备注内容',fontsize,color,..) #给右下角添加备注
#想说的说这里面有连个参数,分别以列表的形式展示。
现在只需要介绍是用来设置坐标刻度的。其中第二个参数列表
是用来转义的。具体实例如下。
下面展示一些 内联代码片。
在x轴的刻度上,我们需要我们需要按照规则的正余弦刻度来,而不是简单的实数,我们需要圆周率。因此在plt.xticks([],[])的第二个列表参数上需要转义。
#这里的r’$代表开始,$代表结尾,\代表转义,\pi代表圆周率的意思,r代表原始字符串。因此可以一一对应下来的。
plt.xticks([-np.pi,-np.pi/2,0,np.pi/2,np.pi],
[r'$-\pi$',r'$-\pi/2$',r'$0$',r'$\pi/2$',r'$\pi$'])
plt.yticks([-1,0,1])
plt.title("绘图正余弦函数曲线图",fontsize = 16,color ='green')
#给图片右下角添加备注标签
plt.text(+2.2,-1.4,"by:jgdabc",fontsize=16,color = 'purple')
plt.show()
第四步:
用到的方法及参数:
plt.gca()#这个方法有点东西。
我要简单的去理解,Python库太繁杂了。有点头大。
plt.gca(),可以获得axes对象
什么又是axes对象?
在matplotlib中,整个图表为一个figure对象。每个figure
对象中可以包含一个或多个axes,而axes为坐标轴。每个axes
对象都是一个拥有自己坐标系统的绘图区域。我们可以理解为通
过这个方法我们可以获得axes对象,而通过这个对象可以帮助我们
方便的操作坐标轴,ok。具体操作看实例吧!
#我相信能看懂英文的不看注释也可以看懂
ax = plt.gca() #获取Axes对象
ax.spines['right'].set_color('none') #隐藏右边界
ax.spines['top'].set_color('none')#隐藏上边界
ax.xaxis.set_ticks_position('bottom') #x轴坐标刻度设置在坐标下面
ax.spines['bottom'].set_position(('data',0))#将x坐标轴平移经过(0,0)的位置
ax.yaxis.set_ticks_position('left')#将y轴坐标刻度设置在坐标轴左侧
ax.spines['left'].set_position(('data',0))#将y轴坐标轴平移到(0,0)位置
plt.show()
兄弟们是不是有点像了,还不够。
第五步:
用到的方法及参数:
plt.legend()
添加图例
这样才会把我上述label的内容显示出来。
plt.legend(loc ='upper left',fontsize=12)
plt.show()
第六步
注意第六步我们要描点,并画线
用到的方法及参数
plt.plot() # 这个前面已经有说明,不再赘述,这里我们
要加一个参数linewidth指定,将其变为虚线
plt.scatter() #用来绘制两个点的位置
plt.annotate #用来添加注释文字,具体解释我们在实例代码中说明
```javascript
t1 = 2*np.pi/3 #取一个x轴坐标点
t2 = -np.pi # 取第二个坐标点
#根据画线,第一个列表是x轴坐标值,第二个列表是y轴坐标值
plt.plot([t1,t1],[0,np.sin(t1)],color = 'b',linewidth = 1.5,linestyle = '--')
#画线
plt.plot([t2,t2],[0,np.cos(t2)],color ='r',linewidth=1.5,linestyle="--")
#标注两个点的位置(绘制散点图的方法)
plt.scatter([t1,],[np.sin(t1),],50,color = 'b') #50为指定的大小
#为图表添加注释
plt.scatter([t2,],[np.cos(2),],50,color = 'r')
plt.annotate( r'$\sin(\frac{2\pi}{3}=\frac{\sqrt{3}}{2}$)',
xy = (t1,np.sin(t1)), #点的位置
xycoords = 'data', #注释文字的偏移量
xytext = (+10,+30), #文字离点的横纵距离
textcoords = 'offset points',
fontsize =14,#注释的大小
arrowprops = dict(arrowstyle = '->',connectionstyle = 'arc3,rad=.2')#箭头指向的弯曲度
)
plt.annotate(r'$\cos(-\pi)=-1$',
xy = (t2,np.cos(t2)),
xycoords = 'data', #注释文字的偏移量
xytext = (0 ,-40), # 文字离点的横纵距离
textcoords = 'offset points',
fontsize = 14,#注释的大小
arrowprops = dict(arrowstyle = '->',connectionstyle='arc3,rad=.2')
) #点的位置
plt.show()
第七步:我想设置一下x轴和y轴的字体,一提到轴,就用ax.
我们直接上代码去解释
#遍历获取x轴和y轴的刻度,并设置字体
for label in ax.get_xticklabels() + ax.get_yticklabels() :
label.set_fontsize(18)
label.set_bbox(dict(facecolor = 'r',edgecolor='g',alpha=0.5))#alpha代表透明度
#绘制填充区域
plt.fill_between(x,np.abs(x)<0.5,sin,sin>0.5,color='g',alpha =0.8)
plt.fill_between(x,cos,where = (-2.5<x)&(x<-0.5),color = 'purple')
plt.grid() #绘制网格线
plt.savefig("D:\python学习数据可视化matplot学习.png",dpi = 300)保存图片
plt.show()
注意这里保存一定要先保存,后show。
最终效果
给大家完整代码
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False
x = np.linspace(-np.pi,np.pi,256,endpoint=256)
sin,cos = np.sin(x),np.cos(x)
plt.plot(x,sin,color = 'blue',lw=2.5,label = '正弦sin',mec='red')
plt.plot(x,cos,color = 'red',lw = 2.5,label = '余弦cos()')
plt.xlim(x.min()*1.5,x.max()*1.5)
plt.ylim(cos.min()*1.5,cos.max()*1.5)
plt.xticks([-np.pi,-np.pi/2,0,np.pi/2,np.pi],[r'$-\pi$',r'$-\pi/2$',r'$0$',r'$\pi/2$',r'$\pi$'])
plt.yticks([-1,0,1])
plt.title("绘图正余弦函数曲线图",fontsize = 16,color ='green')
plt.text(+2.2,-1.4,"by:jgdabc",fontsize=16,color = 'purple')
ax = plt.gca()
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0))
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0))
plt.legend(loc ='upper left',fontsize=12)
t1 = 2*np.pi/3
t2 = -np.pi
plt.plot([t1,t1],[0,np.sin(t1)],color = 'b',linewidth = 1.5,linestyle = '--')
plt.plot([t2,t2],[0,np.cos(t2)],color ='r',linewidth=1.5,linestyle="--")
plt.scatter([t1,],[np.sin(t1),],50,color = 'b')
plt.scatter([t2,],[np.cos(2),],50,color = 'r')
plt.annotate( r'$\sin(\frac{2\pi}{3}=\frac{\sqrt{3}}{2}$)',
xy = (t1,np.sin(t1)),
xycoords = 'data',
xytext = (+10,+30),
textcoords = 'offset points',
fontsize =14,
arrowprops = dict(arrowstyle= '->',connectionstyle = 'arc3,rad=.2')#箭头指向的弯曲度
)
plt.annotate(r'$\cos(-\pi)=-1$',
xy = (t2,np.cos(t2)),
xycoords = 'data',
xytext = (0 ,-40),
textcoords = 'offset points',
fontsize = 14,
arrowprops = dict(arrowstyle = '->',connectionstyle='arc3,rad=.2')
)
for label in ax.get_xticklabels() + ax.get_yticklabels() :
label.set_fontsize(18)
label.set_bbox(dict(facecolor = 'r',edgecolor='g',alpha=0.5))
plt.fill_between(x,np.abs(x)<0.5,sin,sin>0.5,color='g',alpha =0.8)
plt.fill_between(x,cos,where = (-2.5<x)&(x<-0.5),color = 'purple')
plt.grid()
plt.savefig("D:\python学习数据可视化matplot学习.png",dpi = 300)
plt.show()
感谢大家阅读!!!
多说一句,很多人学Python过程中会遇到各种烦恼问题,没有人解答容易放弃。小编是一名python开发工程师,这里有我自己整理了一套最新的python系统学习教程,包括从基础的python脚本到web开发、爬虫、数据分析、数据可视化、机器学习等。想要这些资料的可以关注小编,并在后台私信小编:“01”即可领取。
华硕冠名,中关村在线携手华硕、七彩虹等知名硬件厂商在年底推出的重磅活动——2015年度游戏攒机大赛已经完美落幕。其中冠军配置已经揭晓,获奖网友为:默默的走路。目前冠军配置硬件已由华硕提供至ZOL,下面我们就来为大家进行装机配置直播。
10:09 2016-01-22
玩家们稍等片刻,我们的编辑同学正在把硬件拆包以及搭建拍摄台,10:30我们将正式开启装机直播~
10:13 2016-01-22
在等待的过程中我们在回顾一下2015年华硕杯攒机大赛的盛况吧!http://diy.zol.com.cn/topic/5544554.html#t1
10:25 2016-01-22
我想现在直播前最为激动的网友一定是冠军:默默的走路。将会在接下来的时间里亲眼目睹自己的游戏装备被搭建的全过程~
10:31 2016-01-22
好了,身在ZOL评测室的编辑已经准备就绪,下面我们就来一睹2015年华硕杯攒机大赛冠军配置装机全过程吧!
10:36 2016-01-22
下面我们来看一下这套主机的所有配件吧!CPU:Intel i3-4170 ,主板:华硕B85-G,内存:威刚 游戏威龙8G DDR3 1600,硬盘:希捷1T 7200转 64M,SSD:闪迪120G,显卡:华硕猛禽GTX960-DC2OC-2GD5,机箱:先马 碳立方,电源:航嘉 冷静王至强版
10:40 2016-01-22
在开始装机之前我们来插播一条温馨提示哈~此次大赛除了冠军获得者以外,想必其他获奖网友都收到了相应奖品,如果没收到的玩家请联系:ZOL邱邱,他的邮箱是:qiu.shiqian@zol.com.cn
10:44 2016-01-22
对于主流游戏玩家一定非常关注显卡的性能,这款华硕STRIX-GTX 960-DC2OC-2GD5,在核心方面这款显卡采用了最新的第二代麦克斯韦架构GM206图形核心,具备更低的功耗,经济性更好。6pin电源接口的设置也为这款显卡的超频操作做出了准备。更主要的是具有蛮高的性价比。
10:48 2016-01-22
这次直播我们将记录主机组装的全过程,对于小白玩家来说也是非常实用,如果不太会装机的玩家不妨仔细观看哦~
10:52 2016-01-22
首先,我们先将CPU安装,玩家要注意的是,CPU的三角标与主板上面的三角标对准统一方向,千万不要四个方向乱试!因为针脚很脆弱,一不小心弄完可就麻烦了!
10:54 2016-01-22
然后把拉杆扣下,CPU我们就安装完成啦!
11:00 2016-01-22
然后将CPU风扇安好,因为是原装散热器,所以在散热器底部自带硅脂。如果是选配的散热器玩家一定记得要在安装之前均匀的涂抹硅脂!
11:04 2016-01-22
我看到名为:全真道士行天下的网友,回复说:这配置不便宜吧!其实恰恰相反,这套配置主打的就是网吧游戏型主机高性价比的特点~
11:09 2016-01-22
哦对!在这里提醒玩家一点的就是!此次的CPU散热器采用的是下压式,所以没有朝向的问题。如果玩家购买的是塔式散热器,在风扇朝向方面是有讲究的~各位网友稍等一下哈,我找一下素材,让小白玩家了解一下~
11:15 2016-01-22
塔式散热器的方向能够影响机箱内部的整体风道,所以以单风扇为例这种将CPU风扇面向内存插槽一侧,将废热排出机箱背部出风口是最佳的散热效果。
11:18 2016-01-22
好了 接下来我们就来安装电源了,在这里玩家要注意的是,现在主流机箱基本采用的都是下置电源位设计,电源风扇应该向下安装,因为机箱电源风扇是用来吸风的,如果将电源风扇朝上的话,它就会吸取上方显卡产生的热风,不利于电源散热。记住哦!电源风扇向下安装!重要的事情说三遍!电源风扇向下安装!电源风扇向下安装!电源风扇向下安装!
11:22 2016-01-22
电源安装之后呢,我们就该来安装主板以及跳线了。刚才在评测室装机的同事我跟我说安装过程会稍微慢一下,因为为了能够给冠军获得者一个美观的机箱,他把线要理的更加细致,所以玩家们不要着急哦,稍后我们看看他把线打理的如何吧!
11:26 2016-01-22
其实安装跳线并不复杂,玩家只需要把对应的英文标示接好就可以了,下面我们就来带大家看一看这些英文标示都代表什么意思吧!
POWER SW:机箱面板开机按钮
RESET SW:重启按钮
POWER LED(+/-):电源指示灯
H.D.D LED:硬盘指示灯线 USB:连接机箱USB接口
HD AUDIO:机箱前置音频
11:31 2016-01-22
这样的CPU供电理线的方式还挺好看!拥有同款机箱的玩家你们也是这么做的吗?
11:35 2016-01-22
接下来就是安装显卡了,这块没什么好说的~就是别忘记插供电线~
11:36 2016-01-22
硬盘安装~现在基本上市售的机箱都是免工具安装设计,很方便的~
11:40 2016-01-22
看我们装机的同事多贴心~连SATA线都理的很规整~
11:41 2016-01-22
大家是不是想看看这位装机的暖男长什么样呢?待会我给大家公布哈~
11:44 2016-01-22
在评论中我看到了,Longstrovsky这位网友说:“说好的免工具呢 螺丝不还是自己上”?其实这个不是手拧螺丝,只是4个固定钉~只需要把硬盘架打开,然后对准孔位插上即可固定~
11:49 2016-01-22
好了~机器组装完成啦!这就是2015年华硕杯攒机大赛冠军配置主机,我们将在下周一把这款主机发送给网友:默默的走路。在发送之前,我们也会对这套主机的性能进行一下测试,并且于近日在diy.zol.com.cn进行公布,感兴趣的玩家不妨多多关注!
11:52 2016-01-22
哦对!装机暖男的靓照还没公布~那我们就以他的靓照作为2015年华硕杯攒机大赛冠军配置装机直播的结尾吧!感谢大家的观看!更多精彩内容或者有奖活动请随时关注中关村在线DIY频道:diy.zol.com.cn~
*请认真填写需求信息,我们会在24小时内与您取得联系。