蘋果guide學習筆記1<About Threaded Programming>

Multiple threads can improve an application’s perceived responsiveness.

多線程可以提升應用的感知的回應能力

Multiple threads can improve an application’s real-time performance on multicore systems.

多線程可以提升應用真正的性能在多核系統上

Threading Terminology

多線程的術語

thread線程

The term thread is used to refer to a separate path of execution for code

術語線程就是用于指代碼的單獨執行路徑

process進程

The term process is used to refer to a running executable, which can encompass multiple threads

術語進程用于指運行的可執行文件,其可以包含多個線程

task任務

The term task is used to refer to the abstract concept of work that needs to be performed

術語任務用于指代需要執行的工作的抽象概念

Because threads in a single application share the same memory space, they have access to all of the same data structures. If two threads try to manipulate the same data structure at the same time, one thread might overwrite another’s changes in a way that corrupts the resulting data structure.

因為在一個應用里,線程是共享相同的存儲空間。他們可以訪問全部相同的數據結構。假如2個線程在同一時間試著去操作相同的數據結構,一個線程可能覆蓋了另外一個的更改。破壞生成的數據結構。

Alternative technologies to threads

關于線程,可供選擇的技術

Operation objects

how to use operation objects

you can seeConcurrency Programming Guide.

https://developer.apple.com/library/content/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008091

Grand Central Dispatch

how to use GCD同上鏈接

Idle-time notifications

For tasks that are relatively short and very low priority, idle time notifications let you perform the task at a time when your application is not as busy

對于相對較短或者優先級很低的任務,當你的應用并不是繁忙的,那么空閑時間的通知讓你去執行一次任務

Asynchronous functions

The system interfaces include many asynchronous functions that provide automatic concurrency for you. These APIs may use system daemons and processes or create custom threads to perform their task and return the results to you. (The actual implementation is irrelevant because it is separated from your code.) As you design your application, look for functions that offer asynchronous behavior and consider using them instead of using the equivalent synchronous function on a custom thread.

系統的接口包含了許多異步的,用來給你們提供自動并發的函數/方法。這些apis可能使用系統的守護進程或者程序或者創建定制的線程去執行任務,并且返回結果給你。(實際的實現是不想關的,因為它和你們的代碼是分離的)。當你設計你的程序,查找提供異步特性的函數,那么考慮在定制的線程上使用相同的同步函數去代替直接使用異步特性的函數

Timers定時器

You can use timers on your application’s main thread to perform periodic tasks that are too trivial to require a thread, but which still require servicing at regular intervals. For information on timers, see Timer Sources.

你可以在應用的主線程里使用定時器執行需要一個線程而又麻煩的周期任務,任務在定期的間隔里,仍然會去服務。

Although the underlying implementation mechanism for threads is Mach threads, you rarely (if ever) work with threads at the Mach level. Instead, you usually use the more convenient POSIX API or one of its derivatives. The Mach implementation does provide the basic features of all threads, however, including the preemptive execution model and the ability to schedule threads so they are independent of each other.

雖然線程的底層實現機制是mach線程,很少在mach級別使用線程工作。代替你常常使用更有效的POSIX的API或者它的派生類的其中之一。Mach實現提供了線程所有的基礎特性,包含優先執行模型,并且有能力去調度線程因此它們是相互獨立的。

Cocoa threads使用nsthread和nsobject to spaw a thread

POSIX threads面向c接口

When you create a new thread, you must specify an entry-point function (or an entry-point method in the case of Cocoa threads) for that thread. This entry-point function constitutes the code you want to run on the thread. When the function returns, or when you terminate the thread explicitly, the thread stops permanently and is reclaimed by the system. Because threads are relatively expensive to create in terms of memory and time, it is therefore recommended that your entry point function do a significant amount of work or set up a run loop to allow for recurring work to be performed

當你創建了一個新的線程,你必須給線程指定它的入口函數,這個入口函數由你想要在線程上跑的代碼組成。當你的函數返回,或者當你永久終止了線程,那么線程永久停止了。并且被系統回收了!因為線程在內存和時間方面相對昂貴。因此推薦你的入口函數執行大量的工作或者設置一個run loop用來允許執行重復的工作.

Run Loops

A run loop is a piece of infrastructure used to manage events arriving asynchronously on a thread. A run loop works by monitoring one or more event sources for the thread. As events arrive, the system wakes up the thread and dispatches the events to the run loop, which then dispatches them to the handlers you specify. If no events are present and ready to be handled, the run loop puts the thread to sleep.

run loop是一個基礎設施用于管理在線程上異步到達的事件.一個run loop通過監控線程的一個或多個事件源來工作。當事件達到了,系統喚醒了線程,并且將事件分發到run loop。然后將它們分派到指定的處理程序。假如沒有事件出現并準備好處理。run loop讓線程休眠.

You are not required to use a run loop with any threads you create but doing so can provide a better experience for the user. Run loops make it possible to create long-lived threads that use a minimal amount of resources. Because a run loop puts its thread to sleep when there is nothing to do, it eliminates the need for polling, which wastes CPU cycles and prevents the processor itself from sleeping and saving power.

你不需要使用你創建的任何線程的run loop,但是這樣做能給用戶提供更好的體驗。run loop使得創建使用最少的資源的生命周期較長的線程變得可能。因為一個run loop讓它的線程休眠,它消除了輪詢的需要,這浪費了cpu周期并且防止了處理器本身的睡眠和省電.

To configure a run loop, all you have to do is launch your thread, get a reference to the run loop object, install your event handlers, and tell the run loop to run. The infrastructure provided by OS X handles the configuration of the main thread’s run loop for you automatically. If you plan to create long-lived secondary threads, however, you must configure the run loop for those threads yourself.

要配置一個run loop,所有你需要做的是啟動你的線程,獲取到run loop對象的引用.安裝你的事件處理者,并且告訴run loop可以運行了。os x提供的基礎設施可以自動的處理主線程run loop的配置,假如你計劃創建生命周期長的子線程,你必須自己給那些線程配置run loop,

Synchronization Tools

同步工具

One of the hazards of threaded programming is resource contention among multiple threads. If multiple threads try to use or modify the same resource at the same time, problems can occur. One way to alleviate the problem is to eliminate the shared resource altogether and make sure each thread has its own distinct set of resources on which to operate. When maintaining completely separate resources is not an option though, you may have to synchronize access to the resource using locks, conditions, atomic operations, and other techniques.

線程編程的危險之一是多個線程之間的資源競爭,假如多個線程試著同時使用或者修改資源,那么問題來了,緩解問題的一個方法是完全消除共享的資源,并且確保每個線程有自己獨特的資源集合來操作。當維護完全獨立的資源不是一個選擇,你可能必須使用鎖,條件鎖,原子操作和其他技術來同步對資源的訪問.

Locks provide a brute force form of protection for code that can be executed by only one thread at a time. The most common type of lock is mutual exclusion lock, also known as a mutex. When a thread tries to acquire a mutex that is currently held by another thread, it blocks until the lock is released by the other thread. Several system frameworks provide support for mutex locks, although they are all based on the same underlying technology. In addition, Cocoa provides several variants of the mutex lock to support different types of behavior, such as recursion. For more information about the available types of locks, see Locks.

鎖為一次只能由一個線程執行的代碼提供了強有力的保護形式,最常見的鎖類型是互斥鎖,當一個線程試著去獲取當前由另一個線程持有的互斥鎖,它會阻塞直到鎖被另外一個線程釋放了。幾個系統框架提供了對互斥鎖的支持.雖然它們全部都基于相同的基礎技術,此外,cocoa提供了互斥鎖的幾種方式,以不同類型的行為,如遞歸。有關可用類型的鎖的更多信息,請參閱locks

In addition to locks, the system provides support for conditions, which ensure the proper sequencing of tasks within your application. A condition acts as a gatekeeper, blocking a given thread until the condition it represents becomes true. When that happens, the condition releases the thread and allows it to continue. The POSIX layer and Foundation framework both provide direct support for conditions. (If you use operation objects, you can configure dependencies among your operation objects to sequence the execution of tasks, which is very similar to the behavior offered by conditions.)

除了鎖,系統提供了對條件鎖的支持,確保了你的應用程序內任務的順序排列.條件鎖作為一個守門員,阻塞一個給定的線程,直到它表示的條件為true,當發生了,條件鎖釋放線程,允許它繼續。

posix層和基礎框架都為條件鎖提供直接支持。

Although locks and conditions are very common in concurrent design, atomic operations are another way to protect and synchronize access to data. Atomic operations offer a lightweight alternative to locks in situations where you can perform mathematical or logical operations on scalar data types. Atomic operations use special hardware instructions to ensure that modifications to a variable are completed before other threads have a chance to access it.

雖然鎖和條件鎖在并發設計中非常普遍,原子操作是保護和同步數據訪問的另外一種方式。在可以對標量數據類型執行數據和邏輯的運算情況下,原子操作提供了輕量級的鎖的方法。原子操作使用特殊的硬件指令來確保在其他線程有機會訪問變量之前完成對變量的修改.

Inter-thread Communication

線程間通信

Although a good design minimizes the amount of required communication, at some point, communication between threads becomes necessary. (A thread’s job is to do work for your application, but if the results of that job are never used, what good is it?) Threads may need to process new job requests or report their progress to your application’s main thread. In these situations, you need a way to get information from one thread to another. Fortunately, the fact that threads share the same process space means you have lots of options for communication.

雖然良好的設計使得所需通信的數量最小化,但在某些時刻,線程之間的通信變得有必要。(線程的工作是為你的應用程序做工作,但是如果該工作的結果從未被使用,那么它有什么好處?)線程可能需要處理新的作業請求或將其進度報告給應用程序的主線程。在這些情況下,你需要一種從一個線程獲取信息到另一個線程的方法。幸運的是,線程共享相同的進程空間意味著你有很多選項可以進行通信.

There are many ways to communicate between threads, each with its own advantages and disadvantages. Configuring Thread-Local Storage lists the most common communication mechanisms you can use in OS X. (With the exception of message queues and Cocoa distributed objects, these technologies are also available in iOS.) The techniques in this table are listed in order of increasing complexity.

線程之間有許多方式進行通信,每個線程有它自己的好處和壞處。配置線程本地存儲列出了你可以在os x中使用的最常見的通信機制。(除了消息隊列和cocoa分布式對象之外,這些技術也可以在ios中使用)

Direct messaging

直接通信

Cocoa applications support the ability to perform selectors directly on other threads. This capability means that one thread can essentially execute a method on any other thread. Because they are executed in the context of the target thread, messages sent this way are automatically serialized on that thread. For information about input sources,

cocoa應用程序支持直接在其他線程上執行方法的功能。該功能意味著一個線程可以在任何其他線程上基本上執行一個方法。因為它們是在目標線程的上下人中執行的。所以以這種方式發送的消息將在該線程上自動序列化。有關輸入源的信息see detail

Global variables, shared memory, and objects

全局變量,共享內存和對象

Another simple way to communicate information between two threads is to use a global variable, shared object, or shared block of memory. Although shared variables are fast and simple, they are also more fragile than direct messaging. Shared variables must be carefully protected with locks or other synchronization mechanisms to ensure the correctness of your code. Failure to do so could lead to race conditions, corrupted data, or crashes.

另外一種簡單的方式在兩個線程間傳遞信息是使用全局變量,共享對象或共享內存塊.雖然共享變量是快速和簡單的,但是它們比起直接消息更脆弱。共享變量必須使用鎖或者其他同步機制仔細保護,確保代碼的正確性。否則可能導致競爭條件,數據損壞和崩潰.

Conditions

Conditions are a synchronization tool that you can use to control when a thread executes a particular portion of code. You can think of conditions as gate keepers, letting a thread run only when the stated condition is met. For information on how to use conditions

條件鎖是一個同步的工具,你可以使用它來控制線程什么時候執行特定的代碼部分。你可以將條件鎖視為守門員,只有當滿足規定的條件時才允許線程運行。see detail

Run loop sources源

A custom run loop source is one that you set up to receive application-specific messages on a thread. Because they are event driven, run loop sources put your thread to sleep automatically when there is nothing to do, which improves your thread’s efficiency. For information about run loops and run loop sources

自定義的run loop源是你設置為接收特定于應用程序的消息的源代碼。因為它們是事件驅動的。run loop源可以讓你的線程自動進入休眠狀態,這樣可以提高線程的效率。see detail

Ports and sockets

Port-based communication is a more elaborate way to communication between two threads, but it is also a very reliable technique. More importantly, ports and sockets can be used to communicate with external entities, such as other processes and services. For efficiency, ports are implemented using run loop sources, so your thread sleeps when there is no data waiting on the port. For information about run loops and about port-based input sources, see Run Loops.

基于端口的通信是一種更加精細的兩個線程之間的通信方式,但是它也是一種非常可靠的技術.更重要的是,可以使用端口和套接字與外部實體進行通信,比如其他進程和服務。為了效率,端口是使用run loop源實現的,因此當端口上沒有數據等待時,你的線程將休眠。see detail

Message queues消息隊列

The legacy Multiprocessing Services defines a first-in, first-out (FIFO) queue abstraction for managing incoming and outgoing data. Although message queues are simple and convenient, they are not as efficient as some other communications techniques. For more information about how to use message queues, see Multiprocessing Services Programming Guide.

傳統的多處理服務定義了用于管理傳入和傳出數據的先入先出(FIFO)隊列的抽象。雖然消息隊列簡單方便,但并不像其他一些通信技術那樣有效。see detail

Cocoa distributed objects

Distributed objects is a Cocoa technology that provides a high-level implementation of port-based communications. Although it is possible to use this technology for inter-thread communication, doing so is highly discouraged because of the amount of overhead it incurs. Distributed objects is much more suitable for communicating with other processes, where the overhead of going between processes is already high. For more information, see Distributed Objects Programming Topics

分布式對象是一種cocoa的技術,可提供基于端口的通信的高級實現。雖然可以使用這個技術進行跨線程通信,但是由于其引起的開銷的數量,因此這樣做非常不鼓勵。分布式對象更適合于與其他進程通信,其中進程之間的開銷已經很高了。see detail

Design Tips設計技巧

Avoid Creating Threads Explicitly避免顯示創建線程

Writing thread-creation code manually is tedious and potentially error-prone and you should avoid it whenever possible. OS X and iOS provide implicit support for concurrency through other APIs. Rather than create a thread yourself, consider using asynchronous APIs, GCD, or operation objects to do the work. These technologies do the thread-related work behind the scenes for you and are guaranteed to do it correctly. In addition, technologies such as GCD and operation objects are designed to manage threads much more efficiently than your own code ever could by adjusting the number of active threads based on the current system load.

手動創建線程代碼是乏味且可能出錯,你應該盡可能避免。os x和ios通過其他api提供對并發的隱含支持,不是自己創建一個線程,而是考慮使用異步api,gcd或operation來完成工作。這些技術為你幕后線程相關工作,并保證正確執行.此外,gcd和operation等技術旨在通過根據當前系統負載調整活動線程的數量,從而比你自己的代碼更有效的管理線程.see detail

Keep Your Threads Reasonably Busy

保持線程合理忙

If you decide to create and manage threads manually, remember that threads consume precious system resources. You should do your best to make sure that any tasks you assign to threads are reasonably long-lived and productive. At the same time, you should not be afraid to terminate threads that are spending most of their time idle. Threads use a nontrivial amount of memory, some of it wired, so releasing an idle thread not only helps reduce your application’s memory footprint, it also frees up more physical memory for other system processes to use

假如你決定手動創建和管理線程,記得線程會消耗寶貴的系統資源。你應該盡你最大努力確保你分配的線程相當的長壽命和高效。同時,你不應該害怕終止大部分時間閑置的線程。線程使用非常重要的內存,其中一些內存有線,因此釋放空閑的線程不僅幫助減少你應用的內存占用,還可以釋放更多的物理內存,以供其他系統進程使用.

Avoid Shared Data Structures

避免共享數據結構

The simplest and easiest way to avoid thread-related resource conflicts is to give each thread in your program its own copy of whatever data it needs. Parallel code works best when you minimize the communication and resource contention among your threads.

避免與線程相關的資源沖突的最簡單辦法是讓程序中每一個線程都能夠有它需要的任何數據的copy。當你最小化線程間的通信和資源爭用時,并行代碼的效果最佳.

Creating a multithreaded application is hard. Even if you are very careful and lock shared data structures at all the right junctures in your code, your code may still be semantically unsafe. For example, your code could run into problems if it expected shared data structures to be modified in a specific order. Changing your code to a transaction-based model to compensate could subsequently negate the performance advantage of having multiple threads. Eliminating the resource contention in the first place often results in a simpler design with excellent performance

創建多線程應用程序很困難,即使你在代碼中的所有正確關鍵點非常小心并鎖定共享數據結構,你的代碼仍可能在語義上不安全。比如,如果希望以特定順序去修改共享數據結構,你的代碼可能會遇到問題。將代碼更改為基于事務的模型進行補償可能會導致多個線程的性能優勢無效。首先消除資源爭用通常可以使得簡單的設計有更優的性能.

Threads and Your User Interface線程和你的用戶界面

If your application has a graphical user interface, it is recommended that you receive user-related events and initiate interface updates from your application’s main thread. This approach helps avoid synchronization issues associated with handling user events and drawing window content. Some frameworks, such as Cocoa, generally require this behavior, but even for those that do not, keeping this behavior on the main thread has the advantage of simplifying the logic for managing your user interface

如果你的應用程序有圖形用戶界面,建議你從應用程序主線程接收與用戶相關的事件并且啟動界面更新。這種方法有助于避免與處理用戶事件和繪制窗口內容相關的同步問題。一些框架,比如cocoa,通常需要這個行為,但是對于那些不行的框架,在主線程上保持這個行為具有簡化管理用戶界面的邏輯的優點。

There are a few notable exceptions where it is advantageous to perform graphical operations from other threads. For example, you can use secondary threads to create and process images and perform other image-related calculations. Using secondary threads for these operations can greatly increase performance. If you are not sure about a particular graphical operation though, plan on doing it from your main thread

有一些顯著的例外,從其他線程執行圖像操作是有利的。比如,你可以使用子線程來創建和處理圖像并且執行其他與圖像相關的計算。對這些操作使用子線程可以大大提高性能。假如你不確定某個特定的圖形操作,則計劃從主線程中去執行此操作.

Be Aware of Thread Behaviors at Quit Time

A process runs until all non-detached threads have exited. By default, only the application’s main thread is created as non-detached, but you can create other threads that way as well. When the user quits an application, it is usually considered appropriate behavior to terminate all detached threads immediately, because the work done by detached threads is considered optional. If your application is using background threads to save data to disk or do other critical work, however, you may want to create those threads as non-detached to prevent the loss of data when the application exits.

一個進程運行,直到所有的非分離線程退出。默認情況下,只有應用程序的主線程被創建為非分離。但是你可以創建其他的線程。當用戶退出應用程序,通常認為立即終止全部分離的線程是適當的行為,因為分離線程完成的工作被認為是可選的。如果你的應用程序正在使用后臺線程將數據保存到磁盤或者執行其他關鍵工作,則可能需要將這個線程創建為非分離,以防止程序退出時丟失數據.

Creating threads as non-detached (also known as joinable) requires extra work on your part. Because most high-level thread technologies do not create joinable threads by default, you may have to use the POSIX API to create your thread. In addition, you must add code to your application’s main thread to join with the non-detached threads when they do finally exit. For information on creating joinable threads

將線程創建為非分離需要額外的工作。因為大多數高級的線程技術默認不會給你創建可連接線程。所以你可能必須使用POSIX API創建線程。此外你必須在你的應用程序主線程添加代碼,以便在最終退出時與非分離線程結合。see detail

If you are writing a Cocoa application, you can also use the applicationShouldTerminate: delegate method to delay the termination of the application until a later time or cancel it altogether. When delaying termination, your application would need to wait until any critical threads have finished their tasks and then invoke the replyToApplicationShouldTerminate: method

如果你正在編寫cocoa應用程序,你也可以使用applicationShouldTerminate方法去延遲應用程序的終止,直到稍后再次完全取消。當延遲終止,你的應用程序將需要等到任何關鍵線程完成任務,然后調用replyToApplicationShouldTerminate方法

Handle Exceptions

處理異常

Exception handling mechanisms rely on the current call stack to perform any necessary clean up when an exception is thrown. Because each thread has its own call stack, each thread is therefore responsible for catching its own exceptions. Failing to catch an exception in a secondary thread is the same as failing to catch an exception in your main thread: the owning process is terminated. You cannot throw an uncaught exception to a different thread for processing

異常處理機制依賴當前調用堆棧來執行任何必要的清理,當拋出異常時。因為每個線程有它自己的調用堆棧,所以每個線程都負責捕捉自己的異常。沒有在子線程中捕獲異常與在主線程中無法捕獲異常相同,所有的進程終止了。你不能把一個未捕獲的異常拋到不同的線程進行處理.

If you need to notify another thread (such as the main thread) of an exceptional situation in the current thread, you should catch the exception and simply send a message to the other thread indicating what happened. Depending on your model and what you are trying to do, the thread that caught the exception can then continue processing (if that is possible), wait for instructions, or simply exit

如果你需要通知當前線程中異常情況的另一個線程(如主線程),則應該捕獲異常,并且向另一個線程去發送一條消息,指示發生了什么。根據你的模型和你想做什么,捕獲異常的線程可以繼續處理(如果可能的話),等待指令或者退出.

In some cases, an exception handler may be created for you automatically. For example, the @synchronized directive in Objective-C contains an implicit exception handler

在某些情況下,可以自動為你創建一個異常處理程序,例如,oc中的@synchronized偽指令包含一個隱式異常處理程序。

Terminate Your Threads Cleanly

The best way for a thread to exit is naturally, by letting it reach the end of its main entry point routine. Although there are functions to terminate threads immediately, those functions should be used only as a last resort. Terminating a thread before it has reached its natural end point prevents the thread from cleaning up after itself. If the thread has allocated memory, opened a file, or acquired other types of resources, your code may be unable to reclaim those resources, resulting in memory leaks or other potential problems

線程退出的最佳辦法是自然地通過讓它到達其主入口的結尾。盡管有立刻終止線程的功能.但這些功能只能作為最后的手段使用。在線程達到其自然終點之前終止線程,可防治線程自身清除。如果線程分配了內存,打開一個文件或者獲取了其他類型的資源,你的代碼可能無法收回這些資源,導致內存泄漏或其他潛在問題.

Thread Safety in Libraries

庫的線程安全

Although an application developer has control over whether an application executes with multiple threads, library developers do not. When developing libraries, you must assume that the calling application is multithreaded or could switch to being multithreaded at any time. As a result, you should always use locks for critical sections of code

雖然應用程序開發人員可以控制應用程序是否執行多個線程,但是庫開發人員不會。當開發庫時,你必須假設調用應用程序是多線程的,或者可以隨時切換到多線程。因此你應該始終對關鍵代碼段使用鎖.

For library developers, it is unwise to create locks only when an application becomes multithreaded. If you need to lock your code at some point, create the lock object early in the use of your library, preferably in some sort of explicit call to initialize the library. Although you could also use a static library initialization function to create such locks, try to do so only when there is no other way. Execution of an initialization function adds to the time required to load your library and could adversely affect performance

對于庫開發人員來說,僅當應用程序變為多線程時才創建鎖是不明智的。如果你需要在某些時候鎖定代碼,請在使用庫的早期創建鎖對象,最好在某種顯式調用中初始化庫。雖然你也可以使用靜態庫初始化函數來創建這樣的鎖,但只有當沒有其他的方式時才嘗試這樣做。初始化函數的執行增加了加載庫所需的時間,并可能對性能產生不利影響.

Note: Always remember to balance calls to lock and unlock a mutex lock within your library. You should also remember to lock library data structures rather than rely on the calling code to provide a thread-safe environment

注意:始終記住平衡去調用鎖和解鎖庫中的互斥鎖.你還應該記住鎖定庫數據結構,而不是依靠調用代碼來提供線程安全的環境.

If you are developing a Cocoa library, you can register as an observer for the NSWillBecomeMultiThreadedNotification if you want to be notified when the application becomes multithreaded. You should not rely on receiving this notification, though, as it might be dispatched before your library code is ever called

如果你正在開發Cocoa庫,你可以注冊NSWillBecomeMultiThreadedNotification的觀察者,如果你希望在應用程序變為多線程時收到通知,你不應該依賴于接收此通知,因為它可能會在你的庫代碼被調用之前發送.

最后附上地址:?蘋果文檔地址

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • PLEASE READ THE FOLLOWING APPLE DEVELOPER PROGRAM LICENSE...
    念念不忘的閱讀 13,552評論 5 6
  • **2014真題Directions:Read the following text. Choose the be...
    又是夜半驚坐起閱讀 9,939評論 0 23
  • 推薦給各位科研工作者一本好書:writing science。這本書不同于其他教寫作的書,不是從寫作的角度教你具體...
    石博士閱讀 5,396評論 1 17
  • 第一題 神經系統科學家認為我們的大腦天生就帶固定回路,只要對這種回路加以練習,就能決定我們所獲得的能力,而并不是一...
    小莎妹兒閱讀 190評論 0 0
  • 我想有必要記錄下這一天。這幾天身體上的異常讓我的懷疑越來越重,公交車上每日暈車嘔吐,內衣變小,容易餓愛吃肉,全身疲...
    家君在水之湄閱讀 142評論 0 0