MySQL: kill 會話的實現原理

gaopengtttt發表於2019-01-30

原創水平有限,有誤請指出。僅僅作為學習參考和學習筆記。


  • 原始碼版本 5.7.22
  • 只研究了kill connection的情況。

最近看了丁奇老師的mysql課程中 kill session的部分,在平時的工作的做,我們也經常用kill 命令進行殺掉某些會話,偶爾也會出現狀態還是killed的情況,不由得感覺需要研究一下kill 會話的是如何實現的。剛好丁奇老師的這段提供了理論基礎。

一、簡單的過程梳理和列子

先要簡單的梳理一下語句的執行的生命週期:

打個比方我們以如下的執行計劃為列子:

mysql> desc select * from t1 where name='gaopeng';
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   14 |    10.00 | Using where |+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+1 row in set, 1 warning (1.67 sec)
  • 某個客戶端通過mysql客戶端啟動一個程式,通過socket IP:PORT的方式唯一確認一個mysqld伺服器程式。
  • 伺服器程式mysqld準備好一個執行緒和這個mysql客戶端進行網路通訊。
  • mysql客戶端傳送命令通過mysql net協議到達mysqld伺服器端。
  • mysqld服務端執行緒解包,獲取mysql客戶端傳送過來的命令。
  • mysqld服務端執行緒通過許可權認證,語法語義解析,然後經過物理邏輯優化生成一個執行計劃。
  • loop:
  • mysqld服務端執行緒通過這個執行計劃執行語句,首先innodb層會掃描出第一條資料,返回給mysql層進行過濾也就是是否符合條件name='gaopeng';。
  • 如果符合則返回給 mysql客戶端,如果不符合則繼續loop。
  • 直到loop 結束整個資料返回完成。

這裡涉及到了一個mysql客戶端程式和一個mysqld服務端執行緒,他們通過socket進行通訊。如果我們要要kill某個會話我們顯然一般是新開起來一個mysql客戶端程式連線到mysqld服務端顯然這個時候又需要開啟一個服務端執行緒與其對接來響應你的kill命令那麼這個時候圖如下:

MySQL: kill 會話的實現原理
image.png

如圖我們需要研究的就是執行緒2到底如何作用於執行緒1,實際上執行緒之間共享記憶體很簡單這是執行緒的特性決定的,在MySQL中就共享了這樣一個變數THD::killed,不僅執行緒1可以訪問並且執行緒2也可以訪問 。實際上這種情況就是依賴在程式碼的某些位置做了THD::killed的檢查而實現。先大概先描述一下這種情況kill 會話的過程

  • 執行緒2將THD::killed 設定
  • 執行緒1在innodb層做掃描行的時候每行掃描完成後都會去檢查自己的執行緒是否設定為了KILL_CONNECTION
  • 如果設定為KILL_CONNECTION,那麼做相應的終止過程

二、kill的不同情況

上面已經描述了一個select語句的kill的流程,但是並非都是這種情況,我稍微總結了一下可能的情況:

  • 正在執行命令,如上的select的情況(非Innodb行鎖等待情況)。
  • 正在執行命令,如DML等待(Innodb行鎖等待情況),需要Innodb層喚醒,程式碼則繼續。
  • 正在執行命令,MySQL層進行等待比如sleep命令,需要MySQL層喚醒,程式碼則繼續。
  • 空閒狀態正在等待命令的到來。

注意上面的情況都是待殺執行緒處於的情況,而發起命令的執行緒只有一種方式,就是呼叫kill_one_thread函式。下面我將詳細描述一下。對於喚醒操作參考附錄的內容,我這裡就預設大家都知道了。

三、發起kill命令的執行緒

下面是棧幀:

#0  THD::awake (this=0x7ffe7800e870, state_to_set=THD::KILL_CONNECTION) at /root/mysqlc/percona-server-locks-detail-5.7.22/sql/sql_class.cc:2206#1  0x00000000015d5430 in kill_one_thread (thd=0x7ffe7c000b70, id=18, only_kill_query=false) at /root/mysqlc/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:6859#2  0x00000000015d5548 in sql_kill (thd=0x7ffe7c000b70, id=18, only_kill_query=false) at /root/mysqlc/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:6887
  • kill_one_thread
    這是一個主要的函式,他會根據待殺死的my_thread_id也就是我們kill後面跟的值,獲取這個會話的THD結構體然後呼叫THD::awake函式如下:
tmp= Global_THD_manager::get_instance()->find_thd(&find_thd_with_id);//獲得待殺死的會話的THD結構體tmp->awake(only_kill_query ? THD::KILL_QUERY : THD::KILL_CONNECTION);//呼叫THD::awake命令我們這裡是 THD::KILL_CONNECTION
  • THD::awake
    這是一個主要的函式,這個函式會做將待殺死的會話的THD::killed標記為THD::KILL_CONNECTION,然後關閉socket連線,也就是這裡客戶端程式會收到一個類似如下的錯誤:
ERROR 2013 (HY000): Lost connection to MySQL server during query

然後會終止等待進入innodb連線,然後還會做喚醒操作,關於為什麼要做喚醒操作我們後面再說如下:

 killed= state_to_set; \\這裡設定THD::killed 狀態為 KILL_CONNECTIONvio_cancel(active_vio, SHUT_RDWR); \\關閉socket連線,關閉socket連線後則客戶端連線關閉  /* Interrupt target waiting inside a storage engine. */
  if (state_to_set != THD::NOT_KILLED)
    ha_kill_connection(this); \\lock_trx_handle_waitmysql_mutex_lock(current_mutex);
mysql_cond_broadcast(current_cond); \\做喚醒操作
mysql_mutex_unlock(current_mutex);

四、 待殺死執行緒正在執行命令,如上的select的情況(非Innodb行鎖等待情況)。

這種情況就是通過在程式碼合適的位置檢查返回值完成了,比如下面棧幀:

#0  convert_error_code_to_mysql (error=DB_INTERRUPTED, flags=33, thd=0x7ffe74012f30)
    at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/handler/ha_innodb.cc:2064#1  0x00000000019d651e in ha_innobase::general_fetch (this=0x7ffe7493c960, buf=0x7ffe7493cea0 "\377", direction=1, match_mode=0)
    at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/handler/ha_innodb.cc:9907#2  0x00000000019d658b in ha_innobase::index_next (this=0x7ffe7493c960, buf=0x7ffe7493cea0 "\377")
    at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/handler/ha_innodb.cc:9929

我們可以在函式ha_innobase::general_fetch中找到這部分程式碼如下:

default:
        error = convert_error_code_to_mysql(ret, m_prebuilt->table->flags, m_user_thd);

這裡ret如果等於DB_INTERRUPTED就會進入執行緒退出邏輯,具體邏輯我們後面再看。

而其中DB_INTERRUPTED則代表是被殺死的終止狀態,由如下程式碼設定(所謂的"埋點"):

        if (trx_is_interrupted(prebuilt->trx)) {
            ret = DB_INTERRUPTED;

其中trx_is_interrupted很簡單,程式碼如下:

return(trx && trx->mysql_thd && thd_killed(trx->mysql_thd));
而thd_killed如下:
extern "C" int thd_killed(const MYSQL_THD thd)
{  if (thd == NULL)    return current_thd != NULL ? current_thd->killed : 0;  return thd->killed; //返回了THD::killed}

我們可以看到thd->killed正是我們前面發起kill執行緒設定的THD::killed為THD::KILL_CONNECTION,最終這個錯誤會層層返回,最終導致handle_connection迴圈結束進入終止流程。

五、 待殺死執行緒正在執行命令,如DML等待(Innodb行鎖等待情況),需要Innodb層喚醒,程式碼則繼續。

這種情況和上面類似也是需要檢查執行緒的THD::killed狀態是否是THD::KILL_CONNECTION,但是我們知道如果處於pthread_cond_wait函式等待下,那麼必須有其他執行緒對其做喚醒操作程式碼才會繼續進行不然永遠會不跑到判斷邏輯,我們先來看一下等待棧幀

#0  0x00007ffff7bca68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0#1  0x0000000001ab1d35 in os_event::wait (this=0x7ffe74011f18) at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/include/os0event.h:156#2  0x0000000001ab167d in os_event::wait_low (this=0x7ffe74011f18, reset_sig_count=2)
    at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/os/os0event.cc:131#3  0x0000000001ab1aa6 in os_event_wait_low (event=0x7ffe74011f18, reset_sig_count=0)
    at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/os/os0event.cc:328#4  0x0000000001a7305f in lock_wait_suspend_thread (thr=0x7ffe74005190) at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/lock/lock0wait.cc:387#5  0x0000000001b391fc in row_mysql_handle_errors (new_err=0x7fffec091c4c, trx=0x7fffd78045f0, thr=0x7ffe74005190, savept=0x0)
    at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/row/row0mysql.cc:1312#6  0x0000000001b7c2ea in row_search_mvcc (buf=0x7ffe74010160 "\377", mode=PAGE_CUR_G, prebuilt=0x7ffe74004a20, match_mode=0, direction=0)
    at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/row/row0sel.cc:6318#7  0x00000000019d5443 in ha_innobase::index_read (this=0x7ffe7400e280, buf=0x7ffe74010160 "\377", key_ptr=0x0, key_len=0, find_flag=HA_READ_AFTER_KEY)
    at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/handler/ha_innodb.cc:9536

這種情況就需要有一個執行緒喚醒它,但是這裡喚醒是Innodb層和上面的說的MySQL層喚醒還不是一個事情(後面描述),到底由誰來喚醒它呢,我們可以將斷點設定在:

  • event::broadcast
  • event::signal
    上就可以抓到是誰做的這件事情,原來在Innodb內部會有一個執行緒專門幹這事這個執行緒如下:
#0  os_event::broadcast (this=0x7ffe74011f18) at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/include/os0event.h:166#1  0x0000000001ab1be8 in os_event::set (this=0x7ffe74011f18) at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/include/os0event.h:61#2  0x0000000001ab1a3a in os_event_set (event=0x7ffe74011f18) at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/os/os0event.cc:277#3  0x0000000001a73460 in lock_wait_release_thread_if_suspended (thr=0x7ffe70013360)
    at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/lock/lock0wait.cc:491#4  0x0000000001a6a80d in lock_cancel_waiting_and_release (lock=0x30b1938) at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/lock/lock0lock.cc:6896#5  0x0000000001a736a6 in lock_wait_check_and_cancel (slot=0x7fff0060a2a0) at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/lock/lock0wait.cc:539#6  0x0000000001a7383d in lock_wait_timeout_thread (arg=0x0) at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/lock/lock0wait.cc:599#7  0x00007ffff7bc6aa1 in start_thread () from /lib64/libpthread.so.0#8  0x00007ffff6719bcd in clone () from /lib64/libc.so.6

我們稍微檢查一下lock_wait_check_and_cancel的程式碼就會看到如下:

if (trx_is_interrupted(trx)
        || (slot->wait_timeout < 100000000
        && (wait_time > (double) slot->wait_timeout
           || wait_time < 0))) {        /* Timeout exceeded or a wrap-around in system
        time counter: cancel the lock request queued
        by the transaction and release possible
        other transactions waiting behind; it is
        possible that the lock has already been
        granted: in that case do nothing */
        lock_mutex_enter();
        trx_mutex_enter(trx);        if (trx->lock.wait_lock != NULL && !trx_is_high_priority(trx)) {
            ut_a(trx->lock.que_state == TRX_QUE_LOCK_WAIT);
            lock_cancel_waiting_and_release(trx->lock.wait_lock);
        }
        lock_mutex_exit();
        trx_mutex_exit(trx);
    }

我們看到關鍵地方trx_is_interrupted做了對THD::KILL_CONNECTION的判斷,當然這個執行緒還會做Innodb 行鎖超時的喚醒工作,這個執行緒我們可以看到的如下:

|     35 |         4036 | innodb/srv_lock_timeout_thread  |    NULL | BACKGROUND | NULL   | NULL         |

如果對於正在執行的語句,需要回滾的會在隨後做回滾操作如下:

    if (thd->is_error() || (thd->variables.option_bits & OPTION_MASTER_SQL_ERROR))
      trans_rollback_stmt(thd);

六、Innodb kill 邏輯觸發階段總結

總的說來Innodb中正是通過丁奇老師所說的"埋點"來判斷執行緒是否已經被殺掉,其"埋點"所做的事情就是檢查執行緒的THD::killed狀態是否是THD::KILL_CONNECTION,這種埋點是有檢測週期的,不可能每行程式碼過後都檢查一次所以我大概總結了一下埋點的檢查位置:

  • 每行記錄返回給MySQL層的時候
  • 如果遇到Innodb行鎖處於pthread_cond_wait狀態下,需要srv_lock_timeout_thread執行緒先對其喚醒做broadcast操作

實際上可以全程式碼搜尋什麼時候將ret = DB_INTERRUPTED; 的位置就是Innodb層的"埋點"。

七、待殺死執行緒空閒狀態正在等待命令的到來

這種情況就比較簡單了。在空閒的狀態下,待殺死執行緒會一直堵塞在socket讀上面,因為發起kill執行緒會關閉socket通道,待殺死執行緒可以輕鬆的感知到這件事情,下面是net_read_raw_loop中擷取

  /* On failure, propagate the error code. */ 
  if (count)
  {    /* Socket should be closed. */ 
    net->error= 2;    /* Interrupted by a timeout? */
    if (!eof && vio_was_timeout(net->vio))
      net->last_errno= ER_NET_READ_INTERRUPTED;    else
      net->last_errno= ER_NET_READ_ERROR;#ifdef MYSQL_SERVER
    my_error(net->last_errno, MYF(0)); //這裡觸發#endif
  }

這樣handle_connection迴圈結束,進入終止流程。這種情況會在release_resources中clean_up做回滾操作

八、待殺死執行緒正在執行命令,MySQL層進行等待比如sleep命令,需要MySQL層喚醒,程式碼則繼續。

還記得前面我們的發起kill執行緒呼叫THD::awake的時候最後會做喚醒操作嗎?和Innodb層行鎖等待一樣,如果不喚醒那麼程式碼就沒辦法推進,到達不了Innodb層中設定的埋點位置,下面我用sleep為例進行描述。首先我們先來看看sleep的邏輯,實際上在 Item_func_sleep::val_int 函式中還有如下程式碼:

  timed_cond.set_timeout((ulonglong) (timeout * 1000000000.0));//這裡就將sleep的值春如到了timed_cond這個結構體中
  mysql_cond_init(key_item_func_sleep_cond, &cond); // pthread_cond_init 初始化 cond
  mysql_mutex_lock(&LOCK_item_func_sleep); //加鎖 pthread_mutex_lock  對LOCK_item_func_sleep mutex THD::enter_cond 
  thd->ENTER_COND(&cond, &LOCK_item_func_sleep, &stage_user_sleep, NULL);  //#define ENTER_COND(C, M, S, O) enter_cond(C, M, S, O, __func__, __FILE__, __LINE__)
  //這一步cond會傳遞給THD中其他執行緒也能拿到這個cond了,就可以喚醒它,KILL觸發的時候就需要通過這個條件變數喚醒它
  DEBUG_SYNC(current_thd, "func_sleep_before_sleep");
  error= 0;
  thd_wait_begin(thd, THD_WAIT_SLEEP);  while (!thd->killed)
  {
    error= timed_cond.wait(&cond, &LOCK_item_func_sleep); //這裡看是可等待 及sleep 功能實現 呼叫底層pthread_cond_timedwait函式實現 ,並且可以被條件變數喚醒
    if (error == ETIMEDOUT || error == ETIME)      break;
    error= 0;
  }

這裡我們來證明一下,下面是sleep執行緒的棧幀:

[Switching to Thread 0x7fffec064700 (LWP 4738)]
#0  THD::enter_cond (this=0x7ffe70000950, cond=0x7fffec061510, mutex=0x2e4d6a0, stage=0x2d8b630, old_stage=0x0, src_function=0x1f2598c "val_int", 
    src_file=0x1f232e8 "/root/mysqlc/percona-server-locks-detail-5.7.22/sql/item_func.cc", src_line=6057)
    at /root/mysqlc/percona-server-locks-detail-5.7.22/sql/sql_class.h:3395#1  0x00000000010265d8 in Item_func_sleep::val_int (this=0x7ffe70006210) at /root/mysqlc/percona-server-locks-detail-5.7.22/sql/item_func.cc:6057#2  0x0000000000fafea5 in Item::send (this=0x7ffe70006210, protocol=0x7ffe70001c68, buffer=0x7fffec0619b0)
    at /root/mysqlc/percona-server-locks-detail-5.7.22/sql/item.cc:7564#3  0x000000000156b10c in THD::send_result_set_row (this=0x7ffe70000950, row_items=0x7ffe700055d8)
    at /root/mysqlc/percona-server-locks-detail-5.7.22/sql/sql_class.cc:5026#4  0x0000000001565708 in Query_result_send::send_data (this=0x7ffe700063a8, items=...) at /root/mysqlc/percona-server-locks-detail-5.7.22/sql/sql_class.cc:2932

注意這裡結構體cond=0x7fffec061510的地址,最終他會傳遞到THD中,以致於其他執行緒也能後拿到,我們再來看看THD::awake喚醒的條件變數的地址如下:

[Switching to Thread 0x7fffec0f7700 (LWP 4051)]
Breakpoint 2, THD::awake (this=0x7ffe70000950, state_to_set=THD::KILL_CONNECTION) at /root/mysqlc/percona-server-locks-detail-5.7.22/sql/sql_class.cc:2206......
(gdb) n2288          mysql_cond_broadcast(current_cond);
(gdb) p current_cond
$6 = (mysql_cond_t * volatile) 0x7fffec061510

我們可以到看到也是0x7fffec061510,他們是同一個條件變數,那麼也證明了確實是THD::awake最終喚醒了我們的sleep。程式碼得以繼續,繼續後會達到"埋點",最終handle_connection迴圈終止達到終止流程。

九、待殺死執行緒終止

最終在handle_connection 的迴圈達到退出了條件,進行連線終止邏輯如下:

    {      while (thd_connection_alive(thd)) //
      {        if (do_command(thd))          break;
      }
      end_connection(thd);
    }
    close_connection(thd, 0, false, false);
    thd->get_stmt_da()->reset_diagnostics_area();
    thd->release_resources();
.....
    thd_manager->remove_thd(thd);//這裡從THD連結串列上摘下來,之後 KILLED狀態的執行緒才沒有了。
    Connection_handler_manager::dec_connection_count(extra_port_connection);
....
    delete thd;    if (abort_loop) // Server is shutting down so end the pthread.
      break;
    channel_info= Per_thread_connection_handler::block_until_new_connection();    if (channel_info == NULL)      break;
    pthread_reused= true;

這裡我們發現會經歷幾個函式end_connection/get_stmt_da()->reset_diagnostics_area()/release_resources 然後來到了thd_manager->remove_thd(thd),最終這個連結會被重用。實際上直到release_resources做完我們才會看到show processlist中的狀態消失。可以修改程式碼,在release_resources函式前後加上sleep(10)函式來驗證,如下:

sleep(10);thd->release_resources();sleep(10);

得到的測試結果如下:

mysql> show processlist ; kill 31;kill 33;kill 35;
+----+------+-----------+------+---------+------+----------+------------------+-----------+---------------+| Id | User | Host      | db   | Command | Time | State    | Info             | Rows_sent | Rows_examined |
+----+------+-----------+------+---------+------+----------+------------------+-----------+---------------+
|  7 | root | localhost | NULL | Query   |    0 | starting | show processlist |         0 |             0 || 31 | root | localhost | NULL | Sleep   |   35 |          | NULL             |         1 |             0 |
| 33 | root | localhost | NULL | Sleep   |   32 |          | NULL             |         1 |             0 || 35 | root | localhost | NULL | Sleep   |   29 |          | NULL             |         1 |             0 |
+----+------+-----------+------+---------+------+----------+------------------+-----------+---------------+
mysql> show processlist ;
+----+------+-----------+------+---------+------+-------------+------------------+-----------+---------------+
| Id | User | Host      | db   | Command | Time | State       | Info             | Rows_sent | Rows_examined |+----+------+-----------+------+---------+------+-------------+------------------+-----------+---------------+|  7 | root | localhost | NULL | Query   |    0 | starting    | show processlist |         0 |             0 |
| 31 | root | localhost | NULL | Killed  |   44 | cleaning up | NULL             |         1 |             0 || 33 | root | localhost | NULL | Killed  |   41 | cleaning up | NULL             |         1 |             0 |
| 35 | root | localhost | NULL | Killed  |   38 | cleaning up | NULL             |         1 |             0 |+----+------+-----------+------+---------+------+-------------+------------------+-----------+---------------+4 rows in set (0.02 sec)
mysql> show processlist ;
+----+------+-----------+------+---------+------+----------+------------------+-----------+---------------+| Id | User | Host      | db   | Command | Time | State    | Info             | Rows_sent | Rows_examined |
+----+------+-----------+------+---------+------+----------+------------------+-----------+---------------+
|  7 | root | localhost | NULL | Query   |    0 | starting | show processlist |         0 |             0 |+----+------+-----------+------+---------+------+----------+------------------+-----------+---------------+

可以看到大約10秒後才Killed狀態才消失,而Killed狀態沒有出現20秒因此可以確認是這一步完成後Killed執行緒才會在show processlist中消失。

十、總結

  • kill動作是一個執行緒作用於另外一個執行緒,他們之間的橋樑就是THD:killed這個共享變數。
  • 對於Innodb層的如果有行鎖等待那麼kill會通過執行緒srv_lock_timeout_thread將其喚醒,然後繼續程式碼邏輯。
  • 對於MySQL層的等待同樣需要喚醒這是kill發起命令執行緒完成的,然後繼續程式碼邏輯
  • 將show processlist中的killed狀態的執行緒移除是在整個工作完成之後,比如回滾等
  • kill狀態的響應是通過某些預先設定的檢查點進行的,如果達不到這個檢查點將一直處於Killed狀態
  • 即便檢查點達到,如果在程式碼邏輯中出現其他的Mutex鎖問題得不到退出那麼Killed狀態一直持續如下的列子(BUG?):
    MySQL:kill和show global status命令hang住一列  https://www.jianshu.com/p/70614ae01046

我們有對 thd->release_resources();做詳細研究,就是這個函式執行完成過後Killed執行緒不會出現到show processlist中。如果有同學有興趣可以深入學習。其實在Oracle中我也遇到很多kill session 標記為killed的情況,相信原理都差不多。下次在遇到killed狀態的執行緒也不用急,可以看看這個執行緒是否正在幹活,主要看看這個執行緒是否佔用了CPU了,如果沒有幹活肯可能出現的BUG,比如某些Mutex的鎖等待(如上列)。

附錄:

一些關於posix多執行緒程式設計的函式介面
參考我的文章:
http://blog.itpub.net/7728585/viewspace-2139638/

這裡擷取部分內容:

線上程同步中我們經常會使用到mutex互斥量,其作用用於保護一塊臨界區,避免多執行緒併發操作對這片臨界區帶來的資料混亂,
POSIX的互斥量是一種建議鎖,因為如果不使用互斥量也可以訪問共享資料,但是可能是不安全的。
其原語包含:

  • pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    靜態初始一個互斥量
  • int pthread_mutex_destroy(pthread_mutex_t *mutex);
    銷燬一個互斥量
  • int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
    初始化一個互斥量
  • int pthread_mutex_lock(pthread_mutex_t *mutex);
    互斥量加鎖操作,在這個函式呼叫下的臨界區只允許一個執行緒進行訪問,如果不能獲得鎖則堵塞等待
  • int pthread_mutex_trylock(pthread_mutex_t *mutex);
    互斥量加鎖操作,在這個函式呼叫下的臨界區只允許一個執行緒進行訪問,如果獲得不了鎖則放棄
  • int pthread_mutex_unlock(pthread_mutex_t *mutex);
    互斥量解鎖操作,一般用於在臨界區資料操作完成後解鎖

而條件變數cond則代表當某個條件不滿足的情況下,本執行緒應該放棄鎖,並且將本執行緒堵塞。典型的
生產者消費者問題,如果生產者還沒來得及生產東西,消費者則不應該進行消費操作,應該放棄鎖,將
自己堵塞,直到條件滿足被生產者喚醒。原語包含:

  • pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    靜態初始一個條件變數
  • int pthread_cond_init(pthread_cond_t *cond,const pthread_condattr_t *cattr);
    初始化一個條件變數
  • int pthread_cond_destroy(pthread_cond_t *cond)
    銷燬一個條件變數量
  • int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
    由於某種條件不滿足,解鎖和他繫結互斥量,本執行緒堵塞等待條件成熟被其他執行緒喚醒,如消費者等待生產者生成完成後被喚醒
    注意這裡就繫結了一個互斥量,也就是說條件變數一般和某個互斥量配套使用,因為單獨的條件變數達不到任何堵塞執行緒的目的
  • int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)
    和上面一樣,只是加入了堵塞的時間
  • int pthread_cond_signal(pthread_cond_t *cond)
    條件滿足喚醒由於wait在這個條件變數上某個執行緒,一般用於生產者喚醒消費者
  • int pthread_cond_broadcast(pthread_cond_t *cond)
    條件滿足喚醒由於wait在這個條件變數上全部執行緒,一般用於生產者喚醒消費者

作者微信:gp_22389860


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/7728585/viewspace-2565280/,如需轉載,請註明出處,否則將追究法律責任。

相關文章