iOS并发编程

来源:互联网 发布:万师傅家具安装软件 编辑:程序博客网 时间:2024/05/16 04:09
iOS和OS X使用异步设计方法(asynchronous design approach)去解决并发的问题,而不是直接使用线程。以下是两种解决并发执行的方法:

 

Grand Central Dispatch (GCD)

 

GCD帮组管理线程代码并把这部分代码移到系统层次。开发者需要做的只是定义需要完成的任务然后把其放到合适的dispatch queue(下面简写为DQ)中。GCD负责创建必须的线程和在这些线程上调度任务。

 

 

Operation queues 

 

Operation Queues(下面简写为OQ)是Obj-C的对象,工作原理基本和DQ一样。所有的线程管理都由OQ管理。

 

一些重要概念的简单解释:

 

Dispatch Queues

 

DQ是执行自定义任务的基于C的机制。DQ不管是串行还是并行,都是FIFO的。

 

Dispatch Sources

 

dispatch source是用于处理系统各种特定类型事件的机制。DS封装一些必要的信息,例如系统事件的类型,并会在事件发生的时候,向DQ提交一个特定的block或者函数。

 

系统事件包括:

  • Timers

  • Signal handlers

  • Descriptor-related events

  • Process-related events

  • Mach port events

  • Custom events that you trigger

 

 

Operation Queues

 

OQ是在Cocoa框架中等价于并发dispatch queue的东西,它实现NSOperationQueue。相对于DQ按FIFO的顺序执行任务,OQ引入其他的因素决定执行顺序。最原始的一个因素是任务的执行是否受另外一些任务执行完成的影响。在定义任务的时候配置这些因素并且使用它们去创建一个复杂的执行顺序路径。

 

 

Tips for Improving Efficiency

In addition to simply factoring your code into smaller tasks and adding them to a queue, there are other ways to improve the overall efficiency of your code using queues:

  • Consider computing values directly within your task if memory usage is a factor. If your application is already memory bound, computing values directly now may be faster than loading cached values from main memory. Computing values directly uses the registers and caches of the given processor core, which are much faster than main memory. Of course, you should only do this if testing indicates this is a performance win.

  • Identify serial tasks early and do what you can to make them more concurrent. If a task must be executed serially because it relies on some shared resource, consider changing your architecture to remove that shared resource. You might consider making copies of the resource for each client that needs one or eliminate the resource altogether.

  • Avoid using locks. The support provided by dispatch queues and operation queues makes locks unnecessary in most situations. Instead of using locks to protect some shared resource, designate a serial queue (or use operation object dependencies) to execute tasks in the correct order.

  • Rely on the system frameworks whenever possible. The best way to achieve concurrency is to take advantage of the built-in concurrency provided by the system frameworks. Many frameworks use threads and other technologies internally to implement concurrent behaviors. When defining your tasks, look to see if an existing framework defines a function or method that does exactly what you want and does so concurrently. Using that API may save you effort and is more likely to give you the maximum concurrency possible.

 

 

Dispatch Queues

 

GCD自动提供一些DQ,当然也可以根据需求创建必须的自定义的。

 

 

Table 3-1  Types of dispatch queues

Type

Description

Serial

Serial queues (also known as private dispatch queues) execute one task at a time in the order in which they are added to the queue. The currently executing task runs on a distinct thread (which can vary from task to task) that is managed by the dispatch queue. Serial queues are often used to synchronize access to a specific resource.

You can create as many serial queues as you need, and each queue operates concurrently with respect to all other queues. In other words, if you create four serial queues, each queue executes only one task at a time but up to four tasks could still execute concurrently, one from each queue. For information on how to create serial queues, see “Creating Serial Dispatch Queues.”

Concurrent

Concurrent queues (also known as a type of global dispatch queue) execute one or more tasks concurrently, but tasks are still started in the order in which they were added to the queue. The currently executing tasks run on distinct threads that are managed by the dispatch queue. The exact number of tasks executing at any given point is variable and depends on system conditions.

In iOS 5 and later, you can create concurrent dispatch queues yourself by specifyingDISPATCH_QUEUE_CONCURRENT as the queue type. In addition, there are four predefined global concurrent queues for your application to use. For more information on how to get the global concurrent queues, see “Getting the Global Concurrent Dispatch Queues.”

Main dispatch queue

The main dispatch queue is a globally available serial queue that executes tasks on the application’s main thread. This queue works with the application’s run loop (if one is present) to interleave the execution of queued tasks with the execution of other event sources attached to the run loop. Because it runs on your application’s main thread, the main queue is often used as a key synchronization point for an application.

Although you do not need to create the main dispatch queue, you do need to make sure your application drains it appropriately. For more information on how this queue is managed, see “Performing Tasks on the Main Thread.”

 


DQ的好处是让你更多关注的是执行的任务,而不是去关心线程的创建和管理。

 

 

如果有两个在不同线程的任务需要访问一个共享的资源,如果使用线程需要使用锁去实现这个操作。如果使用DQ,可以把任务放到serial dispatch queue中去,保证一个时刻只有一个任务在访问资源。这相对于使用锁去同步,有很多的性能优势。锁请求昂贵的kernel trap不管是否抢占资源,但是DQ工作于用户进程空间,只有在必须的时候才会向内核调用。

 

对于运行在串行队列的任务,并不是并发的,但相对于使用lock的情况,线程的并发优势会大大降低甚至没有了,更重要的是,线程的创建消耗内核和用户空间的内存。DQ不用为它们的线程牺牲同样的内存,而且DQ使用的线程会保持繁忙并且是不阻塞的。

 

DQ的一些关键点:

 

 

  • Dispatch queues execute their tasks concurrently with respect to other dispatch queues. The serialization of tasks is limited to the tasks in a single dispatch queue.多DQ之间任务是并发的,单DQ内任务是串行的。

  • The system determines the total number of tasks executing at any one time. Thus, an application with 100 tasks in 100 different queues may not execute all of those tasks concurrently (unless it has 100 or more effective cores).

  • The system takes queue priority levels into account when choosing which new tasks to start. For information about how to set the priority of a serial queue, see “Providing a Clean Up Function For a Queue.”

  • Tasks in a queue must be ready to execute at the time they are added to the queue. (If you have used Cocoa operation objects before, notice that this behavior differs from the model operations use.)

  • Private dispatch queues are reference-counted objects. In addition to retaining the queue in your own code, be aware that dispatch sources can also be attached to a queue and also increment its retain count. Thus, you must make sure that all dispatch sources are canceled and all retain calls are balanced with an appropriate release call. For more information about retaining and releasing queues, see“Memory Management for Dispatch Queues.” For more information about dispatch sources, see “About Dispatch Sources.”

 

 

除了DQ外,GCD提供以下技术使用queue去管理代码:

 

 

 

Table 3-2  Technologies that use dispatch queues

Technology

Description

Dispatch groups

A dispatch group is a way to monitor a set ofblock objects for completion. (You can monitor the blocks synchronously or asynchronously depending on your needs.) Groups provide a useful synchronization mechanism for code that depends on the completion of other tasks. For more information about using groups, see “Waiting on Groups of Queued Tasks.”

Dispatch semaphores

A dispatch semaphore is similar to a traditional semaphore but is generally more efficient. Dispatch semaphores call down to the kernel only when the calling thread needs to be blocked because the semaphore is unavailable. If the semaphore is available, no kernel call is made. For an example of how to use dispatch semaphores, see “Using Dispatch Semaphores to Regulate the Use of Finite Resources.”

Dispatch sources

A dispatch source generates notifications in response to specific types of system events. You can use dispatch sources to monitor events such as process notifications, signals, and descriptor events among others. When an event occurs, the dispatch source submits your task code asynchronously to the specified dispatch queue for processing. For more information about creating and using dispatch sources, see “Dispatch Sources.”

Block使用的一些原则:

 

  • For blocks that you plan to perform asynchronously using a dispatch queue, it is safe to capture scalar variables from the parent function or method and use them in the block. However, you should not try to capture large structures or other pointer-based variables that are allocated and deleted by the calling context. By the time your block is executed, the memory referenced by that pointer may be gone. Of course, it is safe to allocate memory (or an object) yourself and explicitly hand off ownership of that memory to the block.对于block,使用父方法的数量值是安全的,不要使用calling context分配和删除的大的结构体或者指针。当block执行的时候,那些指针指向的内存可能已经不存在了。

  • Dispatch queues copy blocks that are added to them, and they release blocks when they finish executing. In other words, you do not need to explicitly copy blocks before adding them to a queue.DQ会复制添加到它们的block。

  • Although queues are more efficient than raw threads at executing small tasks, there is still overhead to creating blocks and executing them on a queue. If a block does too little work, it may be cheaper to execute it inline than dispatch it to a queue. The way to tell if a block is doing too little work is to gather metrics for each path using the performance tools and compare them.不要让block执行开销比dispatch它到queue上还少

  • Do not cache data relative to the underlying thread and expect that data to be accessible from a different block. If tasks in the same queue need to share data, use the context pointer of the dispatch queue to store the data instead. For more information on how to access the context data of a dispatch queue, see “Storing Custom Context Information with a Queue.”

    使用DQ的上下文指针(context pointer)去保存block之间的共享数据,而不是在一个block中缓存它并在另一个block中访问

  • If your block creates more than a few Objective-C objects, you might want to enclose parts of your block’s code in an @autorelease block to handle the memory management for those objects. Although GCD dispatch queues have their own autorelease pools, they make no guarantees as to when those pools are drained. If your application is memory constrained, creating your own autorelease pool allows you to free up the memory for autoreleased objects at more regular intervals.

    如果在block中创建大量的Obj-C对象,请使用@autorelease

Creating and Managing Dispatch Queues

Getting the Global Concurrent Dispatch Queues

 

 

系统提供四条并发DQ。这四条DQ对于应用是全局的,它们之间的区别是优先级。

 

DISPATCH_QUEUE_PRIORITY_DEFAULT

DISPATCH_QUEUE_PRIORITY_HIGH

DISPATCH_QUEUE_PRIORITY_LOW

DISPATCH_QUEUE_PRIORITY_BACKGROUND

 

Creating Serial Dispatch Queues

 

相对于并发DQ,串行DQ是要自己显式创建和管理的。

 

 

Storing Custom Context Information with a Queue

 

 

所有的dispatch对象运行你关联自定义的上下文数据到该对象上。使用方法:

 

dispatch_set_context

dispatch_get_context

 

系统不会使用这些自定义的数据,这些数据的分配和析构都取决于你。

 

Providing a Clean Up Function For a Queue

 

创建串行DQ可以指定回收函数在queue被析构的时候,去做自定义的清理工作。

Listing 3-3  Installing a queue clean up function

void myFinalizerFunction(void *context)
{
    MyDataContext* theData = (MyDataContext*)context;
 
    // Clean up the contents of the structure
    myCleanUpDataContextFunction(theData);
 
    // Now release the structure itself.
    free(theData);
}
 
dispatch_queue_t createMyQueue()
{
    MyDataContext*  data = (MyDataContext*) malloc(sizeof(MyDataContext));
    myInitializeDataContextFunction(data);
 
    // Create the queue and set the context data.
    dispatch_queue_t serialQueue = dispatch_queue_create("com.example.CriticalTaskQueue", NULL);
    if (serialQueue)
    {
        dispatch_set_context(serialQueue, data);
        dispatch_set_finalizer_f(serialQueue, &myFinalizerFunction);
    }
 
    return serialQueue;
}

 

 

 

Adding Tasks to a Queue

 

Important: You should never call the dispatch_sync ordispatch_sync_f function from a task that is executing in the same queue that you are planning to pass to the function. This is particularly important for serial queues, which are guaranteed to deadlock, but should also be avoided for concurrent queues.

不要在执行中的任务中调用dispatch_sync 和 dispatch_sync_f,而且传递给dispatch_sync 和 dispatch_sync_f的queue参数和这个任务的queue是同一个。

 

 

dispatch_queue_t myCustomQueue;
myCustomQueue = dispatch_queue_create("com.example.MyCustomQueue", NULL);
 
dispatch_async(myCustomQueue, ^{
    printf("Do some work here.\n");
});
 
printf("The first block may or may not have run.\n");
 
dispatch_sync(myCustomQueue, ^{
    printf("Do some more work here.\n");
});
printf("Both blocks have completed.\n");

 

Performing a Completion Block When a Task Is Done

 

被dispatch到queue的任务是与创建它的代码是独立运行的。很多时候,需要在任务执行完成后,执行一个回调。在DQ中,可以使用completion block去取代传统的回调方法。completion block就是需要dispatch到queue中的一段代码。

 

void average_async(int *data, size_t len,
   dispatch_queue_t queue, void (^block)(int))
{
   // Retain the queue provided by the user to make
   // sure it does not disappear before the completion
   // block can be called.
   dispatch_retain(queue);
 
   // Do the work on the default concurrent queue and then
   // call the user-provided block with the results.
   dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      int avg = average(data, len);
      dispatch_async(queue, ^{ block(avg);});
 
      // Release the user-provided queue when done
      dispatch_release(queue);
   });
}

 

 

Performing Loop Iterations Concurrently

 

对于需要执行循环并发的情况,可以使用dispatch_apply 或者 dispatch_apply_f。这适用于执行顺序是不重要的情况。对于串行队列,使用这方法对性能是没有提升的。

 

 

Important: Like a regular for loop, the dispatch_apply anddispatch_apply_f functions do not return until all loop iterations are complete. You should therefore be careful when calling them from code that is already executing from the context of a queue. If the queue you pass as a parameter to the function is a serial queue and is the same one executing the current code, calling these functions will deadlock the queue.

Because they effectively block the current thread, you should also be careful when calling these functions from your main thread, where they could prevent your event handling loop from responding to events in a timely manner. If your loop code requires a noticeable amount of processing time, you might want to call these functions from a different thread.

 

和普通for循环一样,dispatch_apply和dispatch_apply_f会在所有的循环结束之后才会返回。所以,如果传递给dispatch_apply和dispatch_apply_f的queue参数是当前任务运行着的queue,而且这个queue是一个串行queue,那么这个调用这个方法就会导致queue死锁。

 

 

Using Objective-C Objects in Your Tasks

 

每一个dispatch queue持有自己的的autorelease pool。

 

 

 

Using Dispatch Semaphores to Regulate the Use of Finite Resources

在访问一些有限的资源时,就需要用到dispatch semaphores去控制这些任务的数量。dispatch semaphores的好处时减少内核调用,只有在资源不足时才会内核调用并停止线程,知道信号量被标记。

 

使用dispatch semaphores的顺序:

 

  1. When you create the semaphore (using thedispatch_semaphore_create function), you can specify a positive integer indicating the number of resources available.

  2. In each task, call dispatch_semaphore_wait to wait on the semaphore.

  3. When the wait call returns, acquire the resource and do your work.

  4. When you are done with the resource, release it and signal the semaphore by calling the dispatch_semaphore_signalfunction.

// Create the semaphore, specifying the initial pool size
dispatch_semaphore_t fd_sema = dispatch_semaphore_create(getdtablesize() / 2);
 
// Wait for a free file descriptor
dispatch_semaphore_wait(fd_sema, DISPATCH_TIME_FOREVER);
fd = open("/etc/services", O_RDONLY);
 
// Release the file descriptor when done
close(fd);
dispatch_semaphore_signal(fd_sema);

 

dispatch_semaphore_wait会时时资源数减1,如果为负数,通知内核阻塞线程。

Waiting on Groups of Queued Tasks

dispatch group的作用时在某些任何执行完成之前阻塞线程。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
 
// Add a task to the group
dispatch_group_async(group, queue, ^{
   // Some asynchronous work
});
 
// Do some other work while the tasks execute.
 
// When you cannot make any more forward progress,
// wait on the group to block the current thread.
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
 
// Release the group when it is no longer needed.
dispatch_release(group);

Dispatch Queues and Thread Safety

对于线程安全的一些Tips:
  • Dispatch queues themselves are thread safe. In other words, you can submit tasks to a dispatch queue from any thread on the system without first taking a lock or synchronizing access to the queue.

  • Do not call the dispatch_sync function from a task that is executing on the same queue that you pass to your function call. Doing so will deadlock the queue. If you need to dispatch to the current queue, do so asynchronously using the dispatch_asyncfunction.

  • Avoid taking locks from the tasks you submit to a dispatch queue. Although it is safe to use locks from your tasks, when you acquire the lock, you risk blocking a serial queue entirely if that lock is unavailable. Similarly, for concurrent queues, waiting on a lock might prevent other tasks from executing instead. If you need to synchronize parts of your code, use a serial dispatch queue instead of a lock.

  • Although you can obtain information about the underlying thread running a task, it is better to avoid doing so. For more information about the compatibility of dispatch queues with threads, see“Compatibility with POSIX Threads.”

 

Dispatch Sources

 

 

与底层系统交互往往需要耗费大量的时间。内核调用或者其他系统底层调用会导致上下文的改变,这会比在自己进程中的调用有更多的开销。所以很多系统库会提供异步接口去允许程序向系统异步提交请求。
GCD使用blcok和DQ向系统提交请求并得到相应的结果返回。

About Dispatch Sources

dispatch sources是协调系统底层事件处理的基础数据结构。GCD支持以下的类型的dispatch sources:

 

  • Timer dispatch sources generate periodic notifications.

  • Signal dispatch sources notify you when a UNIX signal arrives.

  • Descriptor sources notify you of various file- and socket-based operations, such as:

    • When data is available for reading

    • When it is possible to write data

    • When files are deleted, moved, or renamed in the file system

    • When file meta information changes

  • Process dispatch sources notify you of process-related events, such as:

    • When a process exits

    • When a process issues a fork or exec type of call

    • When a signal is delivered to the process

  • Mach port dispatch sources notify you of Mach-related events.

  • Custom dispatch sources are ones you define and trigger yourself.

 

dispatch source取代异步回调方法,实现响应相关的系统事件。配置dispatch source时,需要指定监听的事件,DQ和用于处理事件的代码。当关心的事件发生时,dispatch source会向指定的DQ提交block或者函数。

 

dispatch source会retain相关联的DQ防止其被释放。

 

为防止DQ事件积压,dispatch sources实现了事件合并方案。如果在旧事件没有被处理或者执行前,新事件到达了,dispatch source会合并两个事件的数据。根据事件类型,可能会取代旧事件或者更新旧事件中的数据。

 

 

Creating Dispatch Sources

 

创建DS的步骤:

 

 

  1. Create the dispatch source using the dispatch_source_createfunction.

  2. Configure the dispatch source:

    • Assign an event handler to the dispatch source; see “Writing and Installing an Event Handler.”

    • For timer sources, set the timer information using thedispatch_source_set_timer function; see “Creating a Timer.”

  3. Optionally assign a cancellation handler to the dispatch source; see “Installing a Cancellation Handler.”

  4. Call the dispatch_resume function to start processing events; see “Suspending and Resuming Dispatch Sources.”

 

 

 

Writing and Installing an Event Handler

 

使用dispatch_source_set_event_handler 或者dispatch_source_set_event_handler_f 去设置事件handler。当事件发生,事件handler会被提交到DQ中等待处理。

 

两种类型的handler:

 

 

// Block-based event handler
void (^dispatch_block_t)(void)
 
// Function-based event handler
void (*dispatch_function_t)(void *)

在事件handler内部,可以从DS中获取关于事件的相关信息。

 

 

dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
                                 myDescriptor, 0, myQueue);
dispatch_source_set_event_handler(source, ^{
   // Get some data from the source variable, which is captured
   // from the parent context.
   size_t estimated = dispatch_source_get_data(source);
 
   // Continue reading the descriptor...
});
dispatch_resume(source);

 

 

事件handler中获取到的变量默认是只读的。虽然block特性支持修改这些变量在特定的情况下,但是不要尝试这样做。DS是异步执行事件handler的,所以内部获取的变量可能在执行的时候已经不存在。

 

以下方法,可以获取事件的信息:

 

 

Table 4-1  Getting data from a dispatch source

Function

Description

dispatch_source_get_handle

This function returns the underlying system data type that the dispatch source manages.

For a descriptor dispatch source, this function returns an int type containing the descriptor associated with the dispatch source.

For a signal dispatch source, this function returns an int type containing the signal number for the most recent event.

For a process dispatch source, this function returns a pid_t data structure for the process being monitored.

For a Mach port dispatch source, this function returns a mach_port_t data structure.

For other dispatch sources, the value returned by this function is undefined.

dispatch_source_get_data

This function returns any pending data associated with the event.

For a descriptor dispatch source that reads data from a file, this function returns the number of bytes available for reading.

For a descriptor dispatch source that writes data to a file, this function returns a positive integer if space is available for writing.

For a descriptor dispatch source that monitors file system activity, this function returns a constant indicating the type of event that occurred. For a list of constants, see thedispatch_source_vnode_flags_tenumerated type.

For a process dispatch source, this function returns a constant indicating the type of event that occurred. For a list of constants, see thedispatch_source_proc_flags_tenumerated type.

For a Mach port dispatch source, this function returns a constant indicating the type of event that occurred. For a list of constants, see thedispatch_source_machport_flags_tenumerated type.

For a custom dispatch source, this function returns the new data value created from the existing data and the new data passed to thedispatch_source_merge_datafunction.

dispatch_source_get_mask

This function returns the event flags that were used to create the dispatch source.

For a process dispatch source, this function returns a mask of the events that the dispatch source receives. For a list of constants, see thedispatch_source_proc_flags_tenumerated type.

For a Mach port dispatch source with send rights, this function returns a mask of the desired events. For a list of constants, see thedispatch_source_mach_send_flags_tenumerated type.

For a custom OR dispatch source, this function returns the mask used to merge the data values.

 

Installing a Cancellation Handler

 

 

Cancellation handler的作用是用于在DS被release前清楚资源。多少类型的DS,Cancellation handler是可选实现的,只有在对需要被更新的DS执行一些自定义行为时需要到。对于使用descriptor或者Mach port的DS而言,必须提供Cancellation handler去关闭它们。

 

可以通过 dispatch_source_set_cancel_handler ordispatch_source_set_cancel_handler_f 设置cancellation handler。

 

 

 

Changing the Target Queue

 

 

可以在任何时候使用dispatch_set_target_queue 函数去修改创建DS时指定的queue。在需要改变DS的事件的优先级的时候,需要做这个操作。

 

这个操作时异步的,并不能保证即时执行。对于已经出列的在等待处理的事件handler,它会继续在前一个queue里面执行。

 

 

 

Associating Custom Data with a Dispatch Source

 

 

可以使用dispatch_set_context函数去关联自定义的数据到DS上。可以使用上文件指针去存储存储数据,如果存储了,旧必须要设置cancellation handler去释放它。

 

 

 

Canceling a Dispatch Source

 

​DS一直处于活动状态知道显式调用 dispatch_source_cancel函数,cancel后还要清除掉它。

 

 

void RemoveDispatchSource(dispatch_source_t mySource)
{
   dispatch_source_cancel(mySource);
   dispatch_release(mySource);
}

 

 

 

Suspending and Resuming Dispatch Sources

 

 

可以使用dispatch_suspend 和 dispatch_resume去暂停和重启DS

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 堵奶了挤不下来怎么办 孕36周胎儿腿短怎么办 孕晚期胎儿腿短怎么办 孕晚期宝宝腿短怎么办 手劳累过度麻痛怎么办 大人发烧40不退怎么办? 颈椎扯的脑袋疼怎么办 孕妇颈椎痛导致失眠怎么办 做完运动脊椎中间痛怎么办? 阴虛阴虚火旺怎么办 脸一躺下就丑怎么办 身体淤堵的厉害怎么办 怀孕晚期脚肿了怎么办 宝宝感冒咳嗽流鼻涕流泪怎么办 孕晚期睡觉习惯面朝右怎么办 鼻涕一直不停的流怎么办 宝宝一直流清鼻涕怎么办 感冒了眼睛酸胀流泪怎么办 五个月宝宝感冒流鼻涕怎么办 六个月宝宝感冒流鼻涕怎么办 一岁宝宝感冒咳嗽流鼻涕怎么办 七个月宝宝感冒流鼻涕咳嗽怎么办 孩子握笔姿势不正确怎么办 走久了脚底板痛怎么办 不会给宝宝拍嗝怎么办 打了肉毒素淤青怎么办 新生儿一吃母乳就睡觉怎么办 10岁半宝宝缺钾怎么办 宝宝吃不到乳晕怎么办 喂母乳乳头破了怎么办 宝宝吸了乳头痛怎么办 喂母乳奶头很疼怎么办 孩子吸的乳头痛怎么办 新生儿一直睡觉不吸吮怎么办 新生儿光睡觉不吃奶怎么办 婴儿不吸母亲的奶怎么办 肾里有结石一直不掉下来怎么办 宝宝拍不出来嗝怎么办 未满月宝宝溢奶怎么办 吃母乳老是吐奶怎么办 宝宝吃母乳吐奶怎么办