Linux 讀寫鎖

来源:https://www.cnblogs.com/xiaoshiwang/archive/2019/06/20/11057552.html
-Advertisement-
Play Games

線程的讀寫鎖函數: 1,讀寫鎖的初始化與銷毀,靜態初始化的話,可以直接使用PTHREAD_RWLOCK_INITIALIZER。 2,用讀的方式加鎖和嘗試(沒鎖上就立即返回)加鎖。 3,用寫的方式加鎖和嘗試(沒鎖上就立即返回)加鎖。 4,解鎖 多個進程在同時讀寫同一個文件,會發生什麼? 例子1:用下 ...


線程的讀寫鎖函數:

1,讀寫鎖的初始化與銷毀,靜態初始化的話,可以直接使用PTHREAD_RWLOCK_INITIALIZER。

#include <pthread.h>
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
           const pthread_rwlockattr_t *restrict attr);
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;

2,用讀的方式加鎖和嘗試(沒鎖上就立即返回)加鎖。

#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

3,用寫的方式加鎖和嘗試(沒鎖上就立即返回)加鎖。

#include <pthread.h>
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

4,解鎖

#include <pthread.h>
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

多個進程在同時讀寫同一個文件,會發生什麼?

例子1:用下麵的例子的執行結果,觀察多個進程在同時讀寫同一個文件,會發生什麼。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define MAXLINE 100
#define FN "num1"

void my_lock(int fd){
  return;
}

void my_unlock(int fd){
  return;
}

int main(int args, char** argv){

  int fd;
  long i,seqno;
  pid_t pid;
  ssize_t n;
  char line[MAXLINE + 1];

  pid = getpid();
  fd = open(FN, O_RDWR, 0664);

  for(i = 0; i < 20; ++i){
    my_lock(fd);

    lseek(fd, 0L, SEEK_SET);
    n = read(fd, line, MAXLINE);
    line[n] = '\0';

    seqno = atol(line);
    printf("%s:pid = %ld, seq = %ld\n", argv[0], (long)pid, seqno);

    seqno++;

    snprintf(line, sizeof(line), "%ld\n", seqno);

    lseek(fd, 0L, SEEK_SET);
    write(fd, line, strlen(line));

    my_unlock(fd);
  }

  return 0;
}

執行方法:同時執行上面例子的程式2次,也就是2個進程同時讀寫同一個文件。

ubuntu$ ./flockmain1 & ./flockmain1 &

執行結果如下,發現2個進程同時讀寫,在①處開始,內核切換進程時,數字亂套了。

ubuntu$ ./flockmain1 & ./flockmain1 &
[1] 4760
[2] 4761
ubuntu$ ./flockmain1:pid = 4761, seq = 1
./flockmain1:pid = 4761, seq = 2
./flockmain1:pid = 4761, seq = 3
./flockmain1:pid = 4761, seq = 4
./flockmain1:pid = 4761, seq = 5
./flockmain1:pid = 4761, seq = 6
./flockmain1:pid = 4761, seq = 7
./flockmain1:pid = 4761, seq = 8
./flockmain1:pid = 4761, seq = 9
./flockmain1:pid = 4761, seq = 10   ------------①
./flockmain1:pid = 4760, seq = 10
./flockmain1:pid = 4761, seq = 11
./flockmain1:pid = 4761, seq = 12
./flockmain1:pid = 4761, seq = 13
./flockmain1:pid = 4761, seq = 14
./flockmain1:pid = 4761, seq = 15
./flockmain1:pid = 4761, seq = 16
./flockmain1:pid = 4761, seq = 17
./flockmain1:pid = 4761, seq = 18
./flockmain1:pid = 4761, seq = 19
./flockmain1:pid = 4761, seq = 20
./flockmain1:pid = 4760, seq = 11
./flockmain1:pid = 4760, seq = 12
./flockmain1:pid = 4760, seq = 13
./flockmain1:pid = 4760, seq = 14
./flockmain1:pid = 4760, seq = 15
./flockmain1:pid = 4760, seq = 16
./flockmain1:pid = 4760, seq = 17
./flockmain1:pid = 4760, seq = 18
./flockmain1:pid = 4760, seq = 19
./flockmain1:pid = 4760, seq = 20
./flockmain1:pid = 4760, seq = 21
./flockmain1:pid = 4760, seq = 22
./flockmain1:pid = 4760, seq = 23
./flockmain1:pid = 4760, seq = 24
./flockmain1:pid = 4760, seq = 25
./flockmain1:pid = 4760, seq = 26
./flockmain1:pid = 4760, seq = 27
./flockmain1:pid = 4760, seq = 28
./flockmain1:pid = 4760, seq = 29

為瞭解決上面的問題,必須對文件的內容進行加鎖。

如何對文件內容加鎖?

使用fcntl函數,它既可以鎖整文件,也可以鎖文件里的某段內容。通過結構體flock來指定要鎖的範圍。如果 whence = SEEK_SET;l_start = 0;l_len = 0;就是鎖定整個文件。

struct flock {
               ...
               short l_type;    /* Type of lock: F_RDLCK,
                                   F_WRLCK, F_UNLCK */
               short l_whence;  /* How to interpret l_start:
                                   SEEK_SET, SEEK_CUR, SEEK_END */
               off_t l_start;   /* Starting offset for lock */
               off_t l_len;     /* Number of bytes to lock */
               pid_t l_pid;     /* PID of process blocking our lock
                                   (set by F_GETLK and F_OFD_GETLK) */
               ...
           };
  • F_SETLK:上鎖。如果發現已經被別的進程上鎖了,就直接返回-1,errno被設置成EACCES或者EAGAIN,不阻塞。
  • F_SETLKW:上鎖。阻塞等待。
  • F_GETLK:得到鎖的狀態。

修改上面的函數my_lock,my_unlock。main函數不變。

例子2:

void my_lock(int fd){
  struct flock lock;
  lock.l_type = F_WRLCK;
  wlock.l_whence = SEEK_SET;
  lock.l_start = 0;
  lock.l_len = 0;
  
  fcntl(fd, F_SETLKW, lock);
}

void my_unlock(int fd){
  struct flock lock;
  lock.l_type = F_UNLCK;
  lock.l_whence = SEEK_SET;
  lock.l_start = 0;
  lock.l_len = 0;

  fcntl(fd, F_SETLK, lock);
}

執行結果如下,發現數字不亂套了。

ubuntu$ ./flockmain & ./flockmain &
[1] 4882
[2] 4883
ubuntu$ ./flockmain:pid = 4883, seq = 1
./flockmain:pid = 4883, seq = 2
./flockmain:pid = 4883, seq = 3
./flockmain:pid = 4883, seq = 4
./flockmain:pid = 4883, seq = 5
./flockmain:pid = 4883, seq = 6
./flockmain:pid = 4883, seq = 7
./flockmain:pid = 4883, seq = 8
./flockmain:pid = 4883, seq = 9
./flockmain:pid = 4883, seq = 10
./flockmain:pid = 4883, seq = 11
./flockmain:pid = 4883, seq = 12
./flockmain:pid = 4883, seq = 13
./flockmain:pid = 4883, seq = 14
./flockmain:pid = 4883, seq = 15
./flockmain:pid = 4883, seq = 16
./flockmain:pid = 4883, seq = 17
./flockmain:pid = 4883, seq = 18
./flockmain:pid = 4883, seq = 19
./flockmain:pid = 4883, seq = 20
./flockmain:pid = 4882, seq = 21
./flockmain:pid = 4882, seq = 22
./flockmain:pid = 4882, seq = 23
./flockmain:pid = 4882, seq = 24
./flockmain:pid = 4882, seq = 25
./flockmain:pid = 4882, seq = 26
./flockmain:pid = 4882, seq = 27
./flockmain:pid = 4882, seq = 28
./flockmain:pid = 4882, seq = 29
./flockmain:pid = 4882, seq = 30
./flockmain:pid = 4882, seq = 31
./flockmain:pid = 4882, seq = 32
./flockmain:pid = 4882, seq = 33
./flockmain:pid = 4882, seq = 34
./flockmain:pid = 4882, seq = 35
./flockmain:pid = 4882, seq = 36
./flockmain:pid = 4882, seq = 37
./flockmain:pid = 4882, seq = 38
./flockmain:pid = 4882, seq = 39
./flockmain:pid = 4882, seq = 40

到此為止,貌似解決了問題,但是如果同時執行例子1和例子2,結果如下,發現還是亂的。

也就是說在協作線程(cooperating processes)間,文件鎖(也叫勸告性上鎖)也起作用的。但是不完全不相關的進程中,文件鎖也不起作用的。如何解決呢?使用強制性上鎖。

ys@ys-VirtualBox:~/IPC$ ./flockmain1 & ./flockmain &
[1] 3602
[2] 3603
ys@ys-VirtualBox:~/IPC$ ./flockmain1:pid = 3602, seq = 1
./flockmain:pid = 3603, seq = 1
./flockmain:pid = 3603, seq = 2
./flockmain:pid = 3603, seq = 3
./flockmain:pid = 3603, seq = 4
./flockmain:pid = 3603, seq = 5
./flockmain:pid = 3603, seq = 6
./flockmain:pid = 3603, seq = 7
./flockmain:pid = 3603, seq = 8
./flockmain:pid = 3603, seq = 9
./flockmain:pid = 3603, seq = 10
./flockmain1:pid = 3602, seq = 2
./flockmain1:pid = 3602, seq = 3
./flockmain1:pid = 3602, seq = 4
./flockmain:pid = 3603, seq = 11
./flockmain:pid = 3603, seq = 12
./flockmain1:pid = 3602, seq = 5
./flockmain:pid = 3603, seq = 13
./flockmain1:pid = 3602, seq = 6
./flockmain1:pid = 3602, seq = 7
./flockmain1:pid = 3602, seq = 8
./flockmain:pid = 3603, seq = 14
./flockmain:pid = 3603, seq = 15
./flockmain1:pid = 3602, seq = 9
./flockmain1:pid = 3602, seq = 10
./flockmain:pid = 3603, seq = 16
./flockmain:pid = 3603, seq = 17
./flockmain1:pid = 3602, seq = 11
./flockmain:pid = 3603, seq = 18
./flockmain1:pid = 3602, seq = 12
./flockmain1:pid = 3602, seq = 13
./flockmain1:pid = 3602, seq = 14
./flockmain:pid = 3603, seq = 19
./flockmain:pid = 3603, seq = 20
./flockmain1:pid = 3602, seq = 15
./flockmain1:pid = 3602, seq = 16
./flockmain1:pid = 3602, seq = 17
./flockmain1:pid = 3602, seq = 18
./flockmain1:pid = 3602, seq = 19
./flockmain1:pid = 3602, seq = 20

第一個問題:假如一個文件被一個進程以讀的方式鎖定,並有另一個進程在等待讀鎖定解鎖後,用寫入的方式鎖定,這時是否允許另一個進程的還以讀的方式取得鎖定?

用例子3來觀察:

#include <time.h>
#include <sys/time.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

void gftime(char* buf){
  struct timeval tv;
  gettimeofday(&tv, NULL);
  long usec = tv.tv_usec;
  struct tm* tm = localtime(&tv.tv_sec);
  sprintf(buf, "%d:%d:%d.%ld",tm->tm_hour, tm->tm_min, tm->tm_sec,usec);

}

int main(){
  char buff[100] = {0};
 
  int fd = open("test.dat", O_RDWR | O_CREAT, 0664);

  struct flock lock;
  
  lock.l_type = F_RDLCK;
  lock.l_whence = SEEK_SET;
  lock.l_start = 0;
  lock.l_len = 0;
  fcntl(fd, F_SETLK, &lock);

  gftime(buff);
  printf("%s: parent has read lock\n", buff);

  //first child
  if(fork() == 0){

    char buf2[100] = {0};
    sleep(1);
    gftime(buf2);
    printf("%s: first child tries to obtain write lock\n", buf2);

    struct flock lock2;
    lock2.l_type = F_WRLCK;
    lock2.l_whence = SEEK_SET;
    lock2.l_start = 0;
    lock2.l_len = 0;
    fcntl(fd, F_SETLKW, &lock2);

    gftime(buf2);
    printf("%s: first child obtains write lock\n", buf2);

    sleep(2);

    lock2.l_type = F_UNLCK;
    lock2.l_whence = SEEK_SET;
    lock2.l_start = 0;
    lock2.l_len = 0;
    fcntl(fd, F_SETLK, &lock2);

    gftime(buf2);
    printf("%s: first child releases write lock\n", buf2);
    
    exit(0);
  }
  //secodn child
  if(fork() == 0){
    char buf1[100] = {0};
    sleep(3);
    gftime(buf1);
    printf("%s: second child tries to obtain read lock\n", buf1);

    struct flock lock1;
    lock1.l_type = F_RDLCK;
    lock1.l_whence = SEEK_SET;
    lock1.l_start = 0;
    lock1.l_len = 0;
    fcntl(fd, F_SETLKW, &lock1);

    gftime(buf1);
    printf("%s: second child obtains read lock\n", buf1);

    sleep(4);

    lock1.l_type = F_UNLCK;
    lock1.l_whence = SEEK_SET;
    lock1.l_start = 0;
    lock1.l_len = 0;
    fcntl(fd, F_SETLK, &lock1);

    gftime(buf1);
    printf("%s: second child release read lock\n", buf1);
    
    exit(0);
  }

  //parent process
  sleep(5);

  lock.l_type = F_UNLCK;
  lock.l_whence = SEEK_SET;
  lock.l_start = 0;
  lock.l_len = 0;
  fcntl(fd, F_SETLK, &lock);

  gftime(buff);
  printf("%s: parent releases read lock\n", buff);
  
  wait(NULL);
  wait(NULL);

  exit(0);
}

在ubuntu上執行結果:

17:49:44.348946: parent has read lock
17:49:45.350191: first child tries to obtain write lock
17:49:47.350155: second child tries to obtain read lock
17:49:47.350409: second child obtains read lock
17:49:49.349442: parent releases read lock
17:49:51.351197: second child release read lock
17:49:51.351582: first child obtains write lock
17:49:53.351689: first child releases write lock

第一個問題的答案:允許另一個進程的還以讀的方式取得鎖定

第二個問題:假如一個文件被一個進程以寫的方式鎖定,這時又有2個進程在等待這個鎖的釋放,其中一個進程是以寫鎖的方式等待,其中另一個進程是以讀鎖的方式等待,哪一個會優先取得鎖?

用例子4來觀察:

#include <time.h>
#include <sys/time.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

void gftime(char* buf){
  struct timeval tv;
  gettimeofday(&tv, NULL);
  long usec = tv.tv_usec;
  struct tm* tm = localtime(&tv.tv_sec);
  sprintf(buf, "%d:%d:%d.%ld",tm->tm_hour, tm->tm_min, tm->tm_sec,usec);

}

int main(){
  char buff[100] = {0};
 
  int fd = open("test.dat", O_RDWR | O_CREAT, 0664);

  struct flock lock;
  
  lock.l_type = F_WRLCK;
  lock.l_whence = SEEK_SET;
  lock.l_start = 0;
  lock.l_len = 0;
  fcntl(fd, F_SETLK, &lock);

  gftime(buff);
  printf("%s: parent has write lock\n", buff);

  //first child
  if(fork() == 0){

    char buf2[100] = {0};
    sleep(1);
    gftime(buf2);
    printf("%s: first child tries to obtain write lock\n", buf2);

    struct flock lock2;
    lock2.l_type = F_WRLCK;
    lock2.l_whence = SEEK_SET;
    lock2.l_start = 0;
    lock2.l_len = 0;
    fcntl(fd, F_SETLKW, &lock2);

    gftime(buf2);
    printf("%s: first child obtains write lock\n", buf2);

    sleep(2);

    lock2.l_type = F_UNLCK;
    lock2.l_whence = SEEK_SET;
    lock2.l_start = 0;
    lock2.l_len = 0;
    fcntl(fd, F_SETLK, &lock2);

    gftime(buf2);
    printf("%s: first child releases write lock\n", buf2);
    
    exit(0);
  }
  //secodn child
  if(fork() == 0){
    char buf1[100] = {0};
    sleep(3);
    gftime(buf1);
    printf("%s: second child tries to obtain read lock\n", buf1);

    struct flock lock1;
    lock1.l_type = F_RDLCK;
    lock1.l_whence = SEEK_SET;
    lock1.l_start = 0;
    lock1.l_len = 0;
    fcntl(fd, F_SETLKW, &lock1);

    gftime(buf1);
    printf("%s: second child obtains read lock\n", buf1);

    sleep(4);

    lock1.l_type = F_UNLCK;
    lock1.l_whence = SEEK_SET;
    lock1.l_start = 0;
    lock1.l_len = 0;
    fcntl(fd, F_SETLK, &lock1);

    gftime(buf1);
    printf("%s: second child release read lock\n", buf1);
    
    exit(0);
  }

  //parent process
  sleep(5);

  lock.l_type = F_UNLCK;
  lock.l_whence = SEEK_SET;
  lock.l_start = 0;
  lock.l_len = 0;
  fcntl(fd, F_SETLK, &lock);

  gftime(buff);
  printf("%s: parent releases write lock\n", buff);
  
  wait(NULL);
  wait(NULL);

  exit(0);
}

在ubuntu上執行結果:

17:49:29.796599: parent has write lock
17:49:30.797099: first child tries to obtain write lock
17:49:32.796885: second child tries to obtain read lock
17:49:34.796868: parent releases write lock
17:49:34.796987: second child obtains read lock
17:49:38.797148: second child release read lock
17:49:38.797297: first child obtains write lock
17:49:40.797727: first child releases write lock

第二個問題的答案:沒有準確答案。在Ubuntu上的執行結果上看,讀鎖優先了,但是,可能在別的環境上又是寫鎖優先。按道理來說應該寫鎖優先吧?

c/c++ 學習互助QQ群:877684253

本人微信:xiaoshitou5854


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • $query1 = Class1::find()->where($where);$query2 = Class1::find()->alias('a')->join('left join', Class2::tableName() . 'as b', 'b.id = a.objId')->selec ...
  • P1020 導彈攔截 鏈接:https://www.luogu.org/problemnew/show/P1020 題意:某導彈攔截系統,它每次所攔截的導彈高度均不能超過前一次所攔截的高度(第一次可以達到任意高度),求該系統最多能攔截幾枚導彈以及最少需要多少個這樣的系統才能攔截所有的導彈。 思路:最 ...
  • 策略模式是針對一組演算法,將每一種演算法都封裝到具有共同介面的獨立的類中,從而是它們可以相互替換。策略模式的最大特點是使得演算法可以在不影響客戶端的情況下發生變化,從而改變不同的功能。 ...
  • 1.range 運算結果: 2. 運算結果: ...
  • 當對象間存在一對多關係時,則使用觀察者模式。比如,當一個對象被修改時,則會自動通知它的依賴對象。觀察者模式屬於行為型模式。 運行結果: ...
  • 定義一個操作中演算法的骨架,而將一些步驟延遲到子類中,模板方法使得子類可以不改變演算法的結構即可重定義該演算法的某些特定步驟。 通俗點的理解就是 :完成一件事情,有固定的數個步驟,但是每個步驟根據對象的不同,而實現細節不同;就可以在父類中定義一個完成該事情的總方法,按照完成事件需要的步驟去調用其每個步驟的 ...
  • 本人剛到大三時前面兩年荒廢了 什麼都沒學到所以打算自學個編程 自己對Java非常感興趣 就打算自學Java 但是一開始看書 有很多看不懂 非常苦惱 也打算過去培訓 但是培訓太貴了 最後打算還是先自學一段時間 不行再去培訓 最後買了一套教程 覺得這套教程非常不錯 老師講解非常細緻 通俗易懂 自學了幾個 ...
  • 關於工作中: 如何展示自己項目中的亮點,技術或者難點: 總結我的經歷和技術倒是可以,但是我做的項目和我會的技術都很平庸,實在找不到亮點怎麼辦? 如果知道了你沒有亮點,也就是知道了你自己欠缺什麼,那麼下次跳槽就努力給自己製造亮點,彌補自己的欠缺就行了。 例如,你可以找一個大數據的工作,這樣你以後就多了 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...