GTK Gossip: GMutex

如果您的程式只是一個單執行緒,單一流程的程式,那麼通常您只要注意到程式邏輯的正確,您的程式通常就可以正確的執行您想要的功能,但當您的程式是多執行緒程式,多流程同時執行時,那麼您就要注意到更多的細節,例如在多執行緒共用同一物件的資料時。

如果一個物件所持有的資料可以被多執行緒同時共享存取時,您必須考慮到「資料同步」 的 問題,所謂資料同步指的是兩份資料的整體性一致,例如物件A有 name與id兩個屬性,而有一份A1資料有name與id的資料要更新物件A的屬性,如果A1的name與id設定給A物件完成,則稱A1與A同步,如 果A1資料在更新了物件的name屬性時,突然插入了一份A2資料更新了A物件的id屬性,則顯然的A1資料與A就不同步,A2資料與A也不同步。

資料在多執行緒下共享時,就容易因為同時多個執行緒可能更新同一個物件的資訊,而造成物件資料的不同步,因為資料的不同步而可能引發的錯誤通常不易察覺, 而且可能是在您程式執行了幾千幾萬次之後,才會發生錯誤,而這通常會發生在您的產品已經上線之後,甚至是程式已經執行了幾年之後。

這邊舉個簡單的例子:

  • gmutex_demo.c
#include <glib.h>

struct _User {
GString *name;
GString *id;
glong count;
};
typedef struct _User User;

void user_set_name_id(User *user, GString *name, GString *id) {
user->name = name;
user->id = id;
if(!user_check_name_id(user)) {
g_print("%d: illegal name or id..../n", user->count);
}
user->count++;
}

gboolean user_check_name_id(User *user) {
return user->name->str[0] == user->id->str[0];
}

gpointer thread1(gpointer user) {
GString *name = g_string_new("Justin Lin");
GString *id = g_string_new("J.L.");
while(TRUE) {
user_set_name_id(user, name, id);
}
}

gpointer thread2(gpointer user) {
GString *name = g_string_new("Shang Hwang");
GString *id = g_string_new("S.H.");
while(TRUE) {
user_set_name_id(user, name, id);
}
}

int main(int argc, char *argv[]) {
GMainLoop *mloop;

if(!g_thread_supported()) {
g_thread_init(NULL);
}

User user;

mloop = g_main_loop_new(NULL, FALSE);

g_thread_create(thread1, &user, FALSE, NULL);
g_thread_create(thread2, &user, FALSE, NULL);

g_main_loop_run(mloop);

return 0;
}


在這個程式中,您可以設定使用者的名稱與縮寫id,並簡單檢查一下名稱與id的第一個字是否相同,單就這個程式本身而言,user_set_name_id()並沒有任何的錯誤,但如果它被 用於多執行緒的程式中,而且同一個物件被多個執行存取時,就會"有可能"發生錯誤,一個執行的可能結果如下 (為簡化範例,並無設置停止條件,請直接Ctrl+C結束程式):

51307: illegal name or id....
94812: illegal name or id....
140423: illegal name or id....
174257: illegal name or id....
214260: illegal name or id....
214260: illegal name or id....
259266: illegal name or id....
314738: illegal name or id....
350144: illegal name or id....
402701: illegal name or id....
444026: illegal name or id....
481165: illegal name or id....
....


看到了嗎?如果以單執行緒的觀點來看,上面的訊息在測試中根本不可能出現,然而在這個程式中卻出現了錯誤,而且重點是,第一次錯誤是發生在第51307次的設定(您的電腦上可能是不同的數字),如果您在程式完成並開始應用之後,這個時間點可能是幾個月甚至幾年之後。

問題出現哪?在於這邊:

void user_set_name_id(User *user, GString *name, GString *id) {
    user->name = name;
    user->id = id;
    if(!user_check_name_id(user)) {
        g_print("%d: illegal name or id..../n", user->count);
    }
    user->count++;
}



雖然您設定給它的參數並沒有問題,在某個時間點時,thread1設定了"Justin Lin", "J.L."給name與id,在進行測試的前一刻,thread2可能此時剛好呼叫user_set_name_id(),在name被設定為"Shang Hwang"時,user_check_name_id()開始執行,此時name等於"Shang Hwang",而id還是"J.L.",所以 user_check_name_id() 就會傳回FALSE,結果就顯示了錯誤訊息。

您必須同步資料對物件的更新,也就是在有一個執行緒正在設定user物件的資料時,不可以又被另一個執行緒同時進行設定,您可以使用GMutex來進行這個動作,例如:

  • gmutex_demo.c
#include <glib.h>

GMutex *mutex = NULL;

struct _User {
GString *name;
GString *id;
glong count;
};
typedef struct _User User;

void user_set_name_id(User *user, GString *name, GString *id) {
if(g_mutex_trylock(mutex) ) {
user->name = name;
user->id = id;
if(!user_check_name_id(user)) {
g_print("%d: illegal name or id..../n", user->count);
}
user->count++;
g_mutex_unlock(mutex);
}
else {
g_usleep(1000);
}
}

gboolean user_check_name_id(User *user) {
return user->name->str[0] == user->id->str[0];
}

gpointer thread1(gpointer user) {
GString *name = g_string_new("Justin Lin");
GString *id = g_string_new("J.L.");
while(TRUE) {
user_set_name_id(user, name, id);
}
}

gpointer thread2(gpointer user) {
GString *name = g_string_new("Shang Hwang");
GString *id = g_string_new("S.H.");
while(TRUE) {
user_set_name_id(user, name, id);
}
}

int main(int argc, char *argv[]) {
GMainLoop *mloop;

if(!g_thread_supported()) {
g_thread_init(NULL);
}

User user;

mloop = g_main_loop_new(NULL, FALSE);

mutex = g_mutex_new();

g_thread_create(thread1, &user, FALSE, NULL);
g_thread_create(thread2, &user, FALSE, NULL);

g_main_loop_run(mloop);

return 0;
}


g_mutex_trylock()會嘗試鎖定GMutex,如果成功就傳回TRUE,並繼續執行程式碼,若此時有其它的執行 緒也嘗試鑜定GMutex,則會傳回FALSE,並無法執行GMutex現已鎖定的程式碼範圍,在這個程式中,則是使用if-else,在無法鎖定時,先 睡眠1毫秒後再嘗試。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值