Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
進程,線程,應用程序的定義網上有很多資料,但是有些抽象。通俗的來講,進程就是 一旦一個應用程序開始運行,那么這個應用程序就會存在一個屬于這個應用程序的進程。線程就是進程中的基本執行單元,每個進程中都至少存在著一個線程,這個線程是根據進程創建而創建的,所以這個線程我們稱之為主線程。那么多線程就是包含有除了主線程之外的其他線程。如果一個線程可以執行一個任務,那么多線程就是可以同時執行多個任務。
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小時內與您取得聯系。