iOS 多線程系列 -- GCD全解二(常用方法)

iOS 多線程系列 -- 基礎概述
iOS 多線程系列 -- pthread
iOS 多線程系列 -- NSThread
iOS 多線程系列 -- GCD全解一(基礎)
iOS 多線程系列 -- GCD全解二(常用方法)
iOS 多線程系列 -- GCD全解三(進階)
iOS 多線程系列 -- NSOperation
測試Demo的GitHub地址

3. GCD常用方法

3.1 Dispatch Group

Dispatch Group 可以讓我們很方便的控制多線程中任務執行順序。假設這樣一種需求,有三個任務OA/OB/OC,我們想讓OC在OAOB執行完畢在執行,有幾種實現方式?方式有很多,詳細看下面總結,其中一種方式我們就可以用Dispatch Group實現。

  • 數據類型:
    • dispatch_group_t : 組對象,調用dispatch_group_create()創建
常用方法:
  • dispatch_group_async/dispatch_group_async_f ,異步方式將代碼塊block放入隊列queue中執行,并將隊列關聯到調度組group。dispatch_group會等和它關聯的所有的dispatch_queue_t上的任務都執行完畢才會發出同步信號,執行dispathc_group_notify的代碼塊block,同時dispatch_group_wati會結束等待。一個group可以關聯多個任務隊列,下面示例代碼中可以查看。
    dispatch_group_async(dispatch_group_t group,dispatch_queue_t queue,dispatch_block_t block);
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        for (int i = 0; i<10; i++) {
            NSLog(@"---group1--%d---%@",i,[NSThread currentThread]);
        }
    });
  • dispatch_group_notify 調度組Dispatch Group中任務執行完畢后,會發出同步信號,執行dispatch_group_notify中的block,以此來實現線程同步,實現多線程任務順序控制。所以上面的OA/OB/OC問題可以這樣:
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
    //任務OA
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
    //任務OB
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    //任務OC
});
  • dispatch_group_wait 同步函數,會阻塞當前線程直到超時或者group任務完成。返回0表示任務完全執行完畢,非0表示超時。如果想要一直等待直到任務完成,可以把第二個時間參數設置為DISPATCH_TIME_FOREVER.因為同步,所以小心死鎖,盡量不要放在主線程調用此方法
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC)); 
  • dispatch_group_enter 手動指示有一個block進入group.調用此函數表示另一個塊通過加入組除dispatch_group_async()之外的一種手段。調用此函數必須是與dispatch_group_leave()平衡,否則調度組會顯示一個有任務,不會調用dispatch_group_notify.
  • dispatch_group_leave 手動指示group中一個block完成.調用此函數表示塊已完成,并通過除dispatch_group_async()之外的方法離開調度組.
  • dispatch_group_enter和dispatch_group_leave的簡單理解
  • 一種不同于dispatch_group_async的操作調度組的方法
  • 當我們調用n次dispatch_group_enter后再調用n次dispatch_group_level時,dispatch_group_notify和dispatch_group_wait會收到同步信號
  • 可以簡單的看做group中有一個表示任務數的屬性operationCount, dispatch_group_enter會讓operationCount加1, dispatch_group_level會讓operationCount減1.當operationCount為0的時候,表示group總任務執行完畢,會同步通知dispatch_group_notify和dispatch_group_wait
  • 測試代碼
- (void)group
{
    NSLog(@"---group-begin---%@",[NSThread currentThread]);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{// 異步添加任務到全局并發隊列,并關聯到調度組
        for (int i = 0; i<2; i++) {
            NSLog(@"---group1--%d---%@",i,[NSThread currentThread]);
        }
    });
    dispatch_group_async(group, dispatch_get_main_queue(), ^{// 異步添加任務到主隊列,并關聯到調度組
        for (int i = 0; i<2; i++) {
            NSLog(@"---group2--%d---%@",i,[NSThread currentThread]);
        }
    });
    NSLog(@"---group middle %@",[NSThread currentThread]);
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // group中的任務都執行完畢后,才回執行這個block
        NSLog(@"---dispatch_group_notify---%@",[NSThread currentThread]);
        dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
            NSLog(@"---group3-----%@",[NSThread currentThread]);
        });
        NSLog(@"---dispatch_group_notify middle %@",[NSThread currentThread]);
        dispatch_group_async(group, dispatch_get_main_queue(), ^{
            NSLog(@"---group4----%@",[NSThread currentThread]);
        });
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"---second dispatch_group_notify---%@",[NSThread currentThread]);
        });
    });
    NSLog(@"---before dispatch_group_wait %@",[NSThread currentThread]);
    dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC)); // 同步函數,會阻塞當前線程,只要超時或者group任務完成,返回0表示任務完全執行完畢,非0表示超時
    NSLog(@"---group-end---%@",[NSThread currentThread]);
}
  • 打印結果:
2017-06-29 10:38:15.572 Test - 多線程[21587:754475] ---group-begin---<NSThread: 0x60000007d200>{number = 1, name = main}
 2017-06-29 10:38:15.573 Test - 多線程[21587:754568] ---group1--0---<NSThread: 0x6000002742c0>{number = 3, name = (null)}
 2017-06-29 10:38:15.573 Test - 多線程[21587:754475] ---group middle <NSThread: 0x60000007d200>{number = 1, name = main}  //①
 2017-06-29 10:38:15.573 Test - 多線程[21587:754568] ---group1--1---<NSThread: 0x6000002742c0>{number = 3, name = (null)}  //②
 2017-06-29 10:38:15.573 Test - 多線程[21587:754475] ---before dispatch_group_wait <NSThread: 0x60000007d200>{number = 1, name = main} //③
 2017-06-29 10:38:20.575 Test - 多線程[21587:754475] ---group-end---<NSThread: 0x60000007d200>{number = 1, name = main} //④
 2017-06-29 10:38:20.576 Test - 多線程[21587:754475] ---group2--0---<NSThread: 0x60000007d200>{number = 1, name = main}
 2017-06-29 10:38:20.576 Test - 多線程[21587:754475] ---group2--1---<NSThread: 0x60000007d200>{number = 1, name = main} //⑤
 2017-06-29 10:38:20.577 Test - 多線程[21587:754475] ---dispatch_group_notify---<NSThread: 0x60000007d200>{number = 1, name = main} //⑥
 2017-06-29 10:38:20.577 Test - 多線程[21587:754475] ---dispatch_group_notify middle <NSThread: 0x60000007d200>{number = 1, name = main}
 2017-06-29 10:38:20.577 Test - 多線程[21587:754568] ---group3-----<NSThread: 0x6000002742c0>{number = 3, name = (null)}
 2017-06-29 10:38:20.578 Test - 多線程[21587:754475] ---group4----<NSThread: 0x60000007d200>{number = 1, name = main}
 2017-06-29 10:38:20.578 Test - 多線程[21587:754475] ---second dispatch_group_notify---<NSThread: 0x60000007d200>{number = 1, name = main} //⑦

  • 打印結果分析:
    • 從①和②打印結果看, dispatch_group_async添加任務是異步的,不會阻塞當前線程
    • 從③和④的打印時間差,可以看出當前線程被阻塞了5s,說明dispatch_group_wait是同步函數,會阻塞線程. 為什么阻塞當前線程的時間內沒有group2輸出,查看添加group2輸出的代碼發現,這個打印任務是異步添加到主線程的,主線程阻塞完畢以后才會繼續打印group2
    • ③的打印順序還可以說明dispatch_group_notify也是異步添加任務,不會阻塞當前線程
    • ⑥的打印是在group1和group2之后得出,notify中的block任務確實是在group關聯的任務執行完畢后才執行
    • group3/group4的打印,以及④的輸出可以看出nofity可以多層嵌套
    • 所有調用dispatch_group_async的地方查看一下代碼會發現,調度組group可以關聯到不同的隊列

3.2 柵欄

dispatch_barrier_sync
  • 在一個dispatch queue分派隊列上提交用于同步執行的block代碼塊,類似dispatch_sync()但是會標記為障礙,可以控制多線程中任務執行順序.執行順序為:在其之前提交的任務先執行 -> dispatch_barrier_sync提交的任務 -> dispatch_barrier_sync之后提交的任務
  • dispatch_barrier_sync是和并發隊列相關聯的,因為串行隊列中的任務本身就是順序執行的,不需要barrier技術
  • 同步提交block到指定queue,會阻塞當前線程直到在他前面提交到此queue的任務執行完畢,然后執行barrier block,然后當前線程才能繼續往下執行任務 , 從下面測試打印結果的⑤⑥⑦,打印barrier之后才會打印after dispatch_barrier_sync可以驗證這一點
  • 函數聲明如下:queue是指定的并發隊列,block是提交的代碼塊
void dispatch_barrier_sync(dispatch_queue_t queue,
        DISPATCH_NOESCAPE dispatch_block_t block);
  • 測試代碼如下:
 NSLog(@"barrierSync begin");
dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);
    
dispatch_async(queue, ^{
    NSLog(@"----1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"----2-----%@", [NSThread currentThread]);
});
NSLog(@"before dispatch_barrier_sync");
dispatch_barrier_sync(queue, ^{
    NSLog(@"----barrier-----%@", [NSThread currentThread]);
});
NSLog(@"after dispatch_barrier_sync");

dispatch_async(queue, ^{
    NSLog(@"----3-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"----4-----%@", [NSThread currentThread]);
});
NSLog(@"barrierSync end");
  • 測試代碼打印如下:
2017-06-29 12:28:15.355 Test - 多線程[22813:835755] barrierSync begin //①
 2017-06-29 12:28:15.355 Test - 多線程[22813:835755] before dispatch_barrier_sync //②
 2017-06-29 12:28:15.355 Test - 多線程[22813:835803] ----2-----<NSThread: 0x6000002692c0>{number = 4, name = (null)} //③
 2017-06-29 12:28:15.355 Test - 多線程[22813:835805] ----1-----<NSThread: 0x608000260bc0>{number = 3, name = (null)} //④
 2017-06-29 12:28:15.356 Test - 多線程[22813:835755] ----barrier-----<NSThread: 0x600000071880>{number = 1, name = main} //⑤
 2017-06-29 12:28:15.356 Test - 多線程[22813:835755] after dispatch_barrier_sync //⑥
 2017-06-29 12:28:15.356 Test - 多線程[22813:835755] barrierSync end //⑦
 2017-06-29 12:28:15.356 Test - 多線程[22813:835805] ----3-----<NSThread: 0x608000260bc0>{number = 3, name = (null)} //⑧
 2017-06-29 12:28:15.356 Test - 多線程[22813:835803] ----4-----<NSThread: 0x6000002692c0>{number = 4, name = (null)} //⑨
dispatch_barrier_async,基本和dispatch_barrier_sync類似的功能,區別在于:
  • dispatch_barrier_async是提交異步執行的block代碼塊,不會阻塞當前線程,從下面測試打印結果的⑤⑥⑦after dispatch_barrier_sync在barrier之前打印可以驗證這一點
  • 測試代碼:
NSLog(@"barrierAsync begin");
dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);
    
dispatch_async(queue, ^{
    NSLog(@"----1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"----2-----%@", [NSThread currentThread]);
});
NSLog(@"before dispatch_barrier_async");
dispatch_barrier_async(queue, ^{
    NSLog(@"----barrier-----%@", [NSThread currentThread]);
});
NSLog(@"after dispatch_barrier_async");
    
dispatch_async(queue, ^{
    NSLog(@"----3-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"----4-----%@", [NSThread currentThread]);
});
NSLog(@"barrierAsync end");
  • 測試結果打印:
2017-06-29 12:29:32.048 Test - 多線程[22813:835755] barrierAsync begin //①
 2017-06-29 12:29:32.049 Test - 多線程[22813:835755] before dispatch_barrier_async //②
 2017-06-29 12:29:32.049 Test - 多線程[22813:835815] ----1-----<NSThread: 0x600000268e00>{number = 5, name = (null)} //③
 2017-06-29 12:29:32.049 Test - 多線程[22813:837436] ----2-----<NSThread: 0x600000267540>{number = 6, name = (null)} //④
 2017-06-29 12:29:32.049 Test - 多線程[22813:835755] after dispatch_barrier_async //⑤
 2017-06-29 12:29:32.049 Test - 多線程[22813:837436] ----barrier-----<NSThread: 0x600000267540>{number = 6, name = (null)} //⑥
 2017-06-29 12:29:32.049 Test - 多線程[22813:835755] barrierAsync end //⑦
 2017-06-29 12:29:32.050 Test - 多線程[22813:837436] ----3-----<NSThread: 0x600000267540>{number = 6, name = (null)} //⑧
 2017-06-29 12:29:32.050 Test - 多線程[22813:835815] ----4-----<NSThread: 0x600000268e00>{number = 5, name = (null)} //⑨
總結:
  • 上面已經得出dispatch_barrier_async和dispatch_barrier_sync的重要區別是會不會阻塞當前線程,所以按需選擇使用哪種barrier技術
  • 如果在主線程需要用到,推薦dispatch_barrier_async因為不會阻塞線程,如果在barrier之前提交的任務中有耗時任務,也不會帶來主線程UI的卡頓

3.3 迭代dispatch_apply

dispatch_apply : 提交block任務塊到調度隊列進行多次調度.

三個參數解析:

  • iterations表示調度總次數;
  • queue任務提交的隊列,如果是并發隊列,那么調度可以并發執行,提高調用次數
  • (^block)(size_t),帶有一個size_t參數的block塊, size_t類型的參數是當前迭代索引 ; block是任務塊
void dispatch_apply(size_t iterations, dispatch_queue_t queue, DISPATCH_NOESCAPE void (^block)(size_t));

NOTE:

  • dispatch_apply是同步函數,會阻塞當前線程,直到迭代完畢,由③可以驗證.
  • dispatch_apply可能會提高迭代速度.你可以指定串行或并發 queue,并發queue允許同時執行多個循環迭代,而串行queue就沒太大必要使用了
  • 如果當前隊列是串行隊列,而且在當前串行隊列中使用dispatch_apply指定當前隊列為迭代隊列,會死鎖
  • 并發隊列中,迭代次序是不定的,有測試打印結果①②可以驗證

測試代碼:

NSLog(@"apply begin");
    dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
        NSLog(@"index = %zd , thread = %@",index,[NSThread currentThread]);
    });
    NSLog(@"apply end");

測試打印結果:

2017-06-29 14:46:08.973 Test -  多線程[24285:925046] apply begin
2017-06-29 14:46:08.973 Test -  多線程[24285:925046] index = 0 , thread = <NSThread: 0x608000079c00>{number = 1, name = main}
2017-06-29 14:46:08.973 Test -  多線程[24285:925297] index = 1 , thread = <NSThread: 0x608000268600>{number = 7, name = (null)} //①
2017-06-29 14:46:08.973 Test -  多線程[24285:925094] index = 3 , thread = <NSThread: 0x60000026cb40>{number = 5, name = (null)} //②
2017-06-29 14:46:08.973 Test -  多線程[24285:925296] index = 2 , thread = <NSThread: 0x60800026ac80>{number = 6, name = (null)}
2017-06-29 14:46:08.974 Test -  多線程[24285:925046] apply end //③

3.4 一次執行dispatch_once

dispatch_once的作用:保證其block代碼塊中的代碼只執行一次,是線程安全的,常用于實現單例等
數據類型
  • dispatch_once_t , 其實就是long類型
typedef long dispatch_once_t;

方法,我們在使用的時候,是一個dispatch_once的宏,對應的是一個_dispatch_once的函數._dispatch_once函數兩個參數,第一個predicate是標志位,第二個是代碼塊。

線程安全簡單理解:
  • 第一次執行,block需要被調用,調用結束后需要置標記變量
  • 非第一次執行,而此時第一次執行尚未完成,線程需要等待第一次執行完成后才能繼續往下執行
  • 非第一次執行,而此時#1已經完成,線程直接跳過block而進行后續任務
  • 更詳細的深入探究可以看這里一步步分析dispatch_once的低負載特性
void
_dispatch_once(dispatch_once_t *predicate,
        DISPATCH_NOESCAPE dispatch_block_t block)
{
    if (DISPATCH_EXPECT(*predicate, ~0l) != ~0l) {
        dispatch_once(predicate, block);
    } else {
        dispatch_compiler_barrier();
    }
    DISPATCH_COMPILER_CAN_ASSUME(*predicate == ~0l);
}

測試代碼

dispatch_apply(4, dispatch_get_global_queue(0, 0), ^(size_t index) {
        NSLog(@"------index = %zd",index);
        static dispatch_once_t onceToken; //①
        dispatch_once(&onceToken, ^{//②
            NSLog(@"------run");
        });
     });

測試打印結果

2017-06-29 15:11:57.542 Test -  多線程[24619:946139] ------index = 1
2017-06-29 15:11:57.542 Test -  多線程[24619:946049] ------index = 0
2017-06-29 15:11:57.542 Test -  多線程[24619:946136] ------index = 2
2017-06-29 15:11:57.542 Test -  多線程[24619:946139] ------run

3.5 延遲 dispatch_after

dispatch_after(dispatch_time_t when,
    dispatch_queue_t queue,
    dispatch_block_t block);
  • dispatch_after,延遲指定時間when后,提交block任務到隊列queue。注意:并不是延時執行任務
  • 測試代碼:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"run-----"); // 延遲2秒后提交任務
    });

3.6 線程狀態操作: 掛起和恢復隊列

  • 當需要掛起隊列時,使用dispatch_suspend方法;
  • 恢復隊列時,使用dispatch_resume方法
注意點:
  • dispatch_suspend 并不會立即掛起隊列,根據API文檔可以查看到。調用dispatch_suspend函數后,當前正在執行的block任務會繼續執行下面的測試打印結果①②可以驗證;只是其后添加的任務會被掛起,直到調用dispatch_resume恢復隊列,從下面的測試打印結果③④⑤順序可以驗證
  • dispatch_suspend 可以掛起串行、并發隊列,但是不能掛起全局并發隊列,下面測試代碼中打開test1可以自行驗證
  • dispatch_suspend 和dispatch_resume必須成對調用。可以簡單理解為:dispatch_suspend會讓隊列的某個計數器加1,dispatch_resume會讓這個計數器減1 , 只有當這個計數器為0的時候才回恢復隊列
  • 以上注意點,可以從下面測試打印結果中驗證。

測試代碼:

-   (void)testsuplend
{
    //test1
    //    dispatch_queue_t queue = dispatch_get_global_queue(0, 0); // dispatch_suspend對全局并發隊列無效
    //test2
    //    dispatch_queue_t queue = dispatch_queue_create(NULL, 0); // dispatch_suspend 對串行隊列有效
    //test3
    dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT); // dispatch_suspend 對自己創建的并發隊列有效
    dispatch_async(queue, ^{
        for (int i = 0 ; i < 10; i ++) {
            NSLog(@"---suplend1 --  %zd thread = %@",i,[NSThread currentThread]);
            sleep(1);
        }
    });
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"dispatch_suspend queue one , suplend1打印任務執行完畢后會掛起隊列,直到dispatch_resume調用后恢復隊列");
        dispatch_suspend(queue);
    });
    
    // 4s后繼續添加新的任務,如果在添加之前點擊掛起,此任務不會執行
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        dispatch_async(queue, ^{
            for (int i = 0 ; i < 10; i ++) {
                sleep(2);
                NSLog(@"---suplend2 --  %zd thread = %@",i,[NSThread currentThread]);
            }
        });
    });
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"dispatch_resume queue one ,---suplend2 --打印任務會恢復執行");
        dispatch_resume(queue);
    });
}

測試打印結果:

2017-06-29 23:31:56.678 Test -  多線程[57328:8371175] ---suplend1 --   0 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}
     2017-06-29 23:31:57.680 Test - 多線程[57328:8371175] ---suplend1 --   1 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}
     2017-06-29 23:31:58.682 Test - 多線程[57328:8371175] ---suplend1 --   2 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}
     2017-06-29 23:31:58.777 Test - 多線程[57328:8371126] dispatch_suspend queue one , suplend1打印任務執行完畢后會掛起隊列,直到dispatch_resume調用后恢復隊列 //①
     2017-06-29 23:31:59.687 Test - 多線程[57328:8371175] ---suplend1 --   3 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}//②
     2017-06-29 23:32:00.689 Test - 多線程[57328:8371175] ---suplend1 --   4 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}
     2017-06-29 23:32:01.690 Test - 多線程[57328:8371175] ---suplend1 --   5 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}
     2017-06-29 23:32:02.677 Test - 多線程[57328:8371126] dispatch_resume queue one ,---suplend2 --打印任務會恢復執行//③
     2017-06-29 23:32:02.692 Test - 多線程[57328:8371175] ---suplend1 --   6 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}
     2017-06-29 23:32:03.695 Test - 多線程[57328:8371175] ---suplend1 --   7 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}//④
     2017-06-29 23:32:04.682 Test - 多線程[57328:8371177] ---suplend2 --   0 thread = <NSThread: 0x60000027b7c0>{number = 4, name = (null)}//⑤
     2017-06-29 23:32:04.699 Test - 多線程[57328:8371175] ---suplend1 --   8 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}
     2017-06-29 23:32:05.702 Test - 多線程[57328:8371175] ---suplend1 --   9 thread = <NSThread: 0x60000027b740>{number = 3, name = (null)}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 在這篇文章中,我將為你整理一下 iOS 開發中幾種多線程方案,以及其使用方法和注意事項。當然也會給出幾種多線程的案...
    張戰威ican閱讀 615評論 0 0
  • 1. GCD簡介 什么是GCD呢?我們先來看看百度百科的解釋簡單了解下概念 引自百度百科:Grand Centra...
    千尋_544f閱讀 402評論 0 0
  • 如果一個人離開你了,就不要去追了。 如果一樣東西不在屬于你,就放棄吧。 是你的終究會回到你身邊,不是你的執著也沒用
    三年玖閱讀 169評論 0 0
  • 叩拜師父 一早有個妹妹發信息,說是自己沒提升,找不到方向了,問我咋辦。我與她關系較親密,又視她如己出,于...
    若尹能量閱讀 1,227評論 0 3
  • 《圓覺經》經典名稱,意義簡述 作者:「憶民-常樂我凈」 日期:2017/2/14 經典名稱:《大方廣圓覺修多羅了義...
    憶民_常樂我淨閱讀 1,496評論 0 3