本文主要讨论的是mysql中的连接模块。 该模块处理客户端与服务端间的通信,并且在服务端为每个客户端连接建立处理线程。
1. 概述
上图为mysql中进程的状态变化图。
1. 初始化一个新创建的线程(当缓存中没有可用的时),然后初始化它,并开始等待客户端请求
2. 一个“waiting for command”的线程会循环等待客户端的命令请求。
3. 当一个客户端命令(query command)到来时,线程的进入“ handle command”状态,调用命令解析模块来处理客户端请求。
4. 请求处理完了以后线程继续等待新的请求。
5. 当一个连接断开时, mysql有2种选择, 一种是直接把这个连接对应的线程杀死,另一种是把这个线程转成一个空闲线程。
6. 这个空闲线程会等待唤醒信号
7. 当接到唤醒信号时,该线程会从系统中获取当前要处理的THD对象,并用它来进行初始化,然后开始进入“waiting for command”状态,等待新的客户端请求。
所以总的来说,mysql中的线程池是一个虚拟的概念, 它是由一群空闲线程构成,并通过互斥信号与条件信号量等线程同步技术来实现空闲线程的调度。 具体的代码在后面的小节中介绍。
2. mysql的线程对象队列
这里的线程对象是指THD对象,而不是真的线程。
mysql中所有的活动THD保存在叫 threads的 I_list队列中,所有的等待被空闲线程接管的THD对象保存在叫 thread_cache的I_List队列中。
3. 初始化线程以处理连接请求 代码导读
相关代码: sql/mysqld.cc 中的 handle_connections_sock (5370行)
mysql服务器端通过I/O多路转接(select/poll)来等待客户端连接请求,然后调用accept获取套接字,并根据该套接字初始化THD对象。 最后调用create_new_thread函数来处理这个THD。
create_new_thread中调用了上面的代码来在服务器端添加一个连接。
thread_scheduler是一个函数指针结构体(sql/scheduler.h )
可以看出create_new_thread函数实际上是调用了 create_thread_to_handle_connection(mysqld.cc 5235行)。
如果有可用的空闲线程,就通过条件信号变量(COND_thread_cache)来唤醒它(后面会介绍)。
否则就新建一个线程, 对应的THD对象添加到threads队列中。新建的线程会调用handle_one_connection (sql/sql_connect.cc)函数来处理连接。而该函数调用了do_handle_one_connection(sql/sql_connect.cc)。
在do_handle_one_connection中首先调用了 “setup_connection_thread_globals(thd)” 来设置该连接对应的全局系统变量(globle_system_variables),为线程私有数据赋值(当前THD对象,与该THD对应的内存管理对象mem_root)。然后进入 for (;;) 循环处理客户端请求。
首先调用login_connection来进行登录验证,然后调用 prepare_new_connection_state来执行一些初始化指令,并且根据系统选项分配该连接线程中使用的内存(mem_root). 然后循环调用do_command来处理客户端命令。
4. 关闭连接与空闲线程处理 代码导读
如果do_command函数返回1(关闭连接)的话首先调用end_connection(thd) 和 close_connection(thd); (都在mysqld.cc中)关闭连接, 然后调用thread_scheduler中的end_thread函数指针(实际上是one_thread_per_connection_end)。
该函数中调用unlink_thd,主要是调整连接计数与活动线程计数。然后调用cache_thread把当前线程转成一个空闲线程。当该线程再次被唤醒并投入使用时返回0.
可以看出,在cache_thread中执行了一个循环来进行条件信号变量(COND_thread_cache)的等待。跳出循环后检查是不是要唤醒,如果唤醒的话,从thread_cache队列中取出一个等待被“领养”的THD对象,然后返回1 (end_thread函数指针返回的是0,所以do_handle_one_connection中的for循环还会继续进行下去,等待客户端请求).
5. 关闭线程代码导读
关闭线程主要有2种情况: 1 不缓存断开连接的线程时; 2 关闭mysqld服务器时。
对于第一种情况在one_thread_per_connection_end函数中, 将不会调用cache_thread函数,而是直接结束这个线程。
对于第二种情况在关闭mysqld服务器的情况:
首先在mysqld服务启动的时候,会启动一个线程专门处理各种信号:
start_signal_handler(); 这个线程的id是signal_thread。
(注mysqld.cc中有2个start_signal_handler函数, 第一个是windows下的,它只是创建了pid文件,第2个是linux下的,会创建一个信号处理线程,调用signal_hand(2689行)函数来创建pid文件并循环等待与处理信号)
以上是处理关闭服务器的请求,可以看出服务器实际上是调用了 kill_server函数(1264行), 关闭mysqld的信号是SIGTERM, SIGQUIT或SIGKILL。
该函数的功能就是关闭所有的连接,杀死所有的线程,然后退出。
当mysqld要推出时(主函数推出或abort), 服务器会直接或间接地调用函数 mysqld_exit(0);
-
该函数首先调用wait_for_signal_thread_to_end关闭活动线程。然后把mysqld启动时分配的锁和日志进行一下清理。
在 wait_for_signal_thread_to_end函数中通过向 信号处理线程(signal_thread,在上面刚讨论的) 发送MYSQL_KILL_SIGNAL。
这个是MYSQL_KILL_SIGNAL的宏定义。
根据上面的讨论,信号处理线程会开始关闭mysqld。
1. 概述

上图为mysql中进程的状态变化图。
1. 初始化一个新创建的线程(当缓存中没有可用的时),然后初始化它,并开始等待客户端请求
2. 一个“waiting for command”的线程会循环等待客户端的命令请求。
3. 当一个客户端命令(query command)到来时,线程的进入“ handle command”状态,调用命令解析模块来处理客户端请求。
4. 请求处理完了以后线程继续等待新的请求。
5. 当一个连接断开时, mysql有2种选择, 一种是直接把这个连接对应的线程杀死,另一种是把这个线程转成一个空闲线程。
6. 这个空闲线程会等待唤醒信号
7. 当接到唤醒信号时,该线程会从系统中获取当前要处理的THD对象,并用它来进行初始化,然后开始进入“waiting for command”状态,等待新的客户端请求。
所以总的来说,mysql中的线程池是一个虚拟的概念, 它是由一群空闲线程构成,并通过互斥信号与条件信号量等线程同步技术来实现空闲线程的调度。 具体的代码在后面的小节中介绍。
2. mysql的线程对象队列
这里的线程对象是指THD对象,而不是真的线程。
mysql中所有的活动THD保存在叫 threads的 I_list队列中,所有的等待被空闲线程接管的THD对象保存在叫 thread_cache的I_List队列中。
3. 初始化线程以处理连接请求 代码导读
相关代码: sql/mysqld.cc 中的 handle_connections_sock (5370行)



mysql服务器端通过I/O多路转接(select/poll)来等待客户端连接请求,然后调用accept获取套接字,并根据该套接字初始化THD对象。 最后调用create_new_thread函数来处理这个THD。

create_new_thread中调用了上面的代码来在服务器端添加一个连接。
thread_scheduler是一个函数指针结构体(sql/scheduler.h )


可以看出create_new_thread函数实际上是调用了 create_thread_to_handle_connection(mysqld.cc 5235行)。

如果有可用的空闲线程,就通过条件信号变量(COND_thread_cache)来唤醒它(后面会介绍)。

否则就新建一个线程, 对应的THD对象添加到threads队列中。新建的线程会调用handle_one_connection (sql/sql_connect.cc)函数来处理连接。而该函数调用了do_handle_one_connection(sql/sql_connect.cc)。
在do_handle_one_connection中首先调用了 “setup_connection_thread_globals(thd)” 来设置该连接对应的全局系统变量(globle_system_variables),为线程私有数据赋值(当前THD对象,与该THD对应的内存管理对象mem_root)。然后进入 for (;;) 循环处理客户端请求。


首先调用login_connection来进行登录验证,然后调用 prepare_new_connection_state来执行一些初始化指令,并且根据系统选项分配该连接线程中使用的内存(mem_root). 然后循环调用do_command来处理客户端命令。
4. 关闭连接与空闲线程处理 代码导读
如果do_command函数返回1(关闭连接)的话首先调用end_connection(thd) 和 close_connection(thd); (都在mysqld.cc中)关闭连接, 然后调用thread_scheduler中的end_thread函数指针(实际上是one_thread_per_connection_end)。

该函数中调用unlink_thd,主要是调整连接计数与活动线程计数。然后调用cache_thread把当前线程转成一个空闲线程。当该线程再次被唤醒并投入使用时返回0.


可以看出,在cache_thread中执行了一个循环来进行条件信号变量(COND_thread_cache)的等待。跳出循环后检查是不是要唤醒,如果唤醒的话,从thread_cache队列中取出一个等待被“领养”的THD对象,然后返回1 (end_thread函数指针返回的是0,所以do_handle_one_connection中的for循环还会继续进行下去,等待客户端请求).
5. 关闭线程代码导读
关闭线程主要有2种情况: 1 不缓存断开连接的线程时; 2 关闭mysqld服务器时。
对于第一种情况在one_thread_per_connection_end函数中, 将不会调用cache_thread函数,而是直接结束这个线程。
对于第二种情况在关闭mysqld服务器的情况:
首先在mysqld服务启动的时候,会启动一个线程专门处理各种信号:
start_signal_handler(); 这个线程的id是signal_thread。

(注mysqld.cc中有2个start_signal_handler函数, 第一个是windows下的,它只是创建了pid文件,第2个是linux下的,会创建一个信号处理线程,调用signal_hand(2689行)函数来创建pid文件并循环等待与处理信号)

以上是处理关闭服务器的请求,可以看出服务器实际上是调用了 kill_server函数(1264行), 关闭mysqld的信号是SIGTERM, SIGQUIT或SIGKILL。

该函数的功能就是关闭所有的连接,杀死所有的线程,然后退出。
当mysqld要推出时(主函数推出或abort), 服务器会直接或间接地调用函数 mysqld_exit(0);
-

该函数首先调用wait_for_signal_thread_to_end关闭活动线程。然后把mysqld启动时分配的锁和日志进行一下清理。

在 wait_for_signal_thread_to_end函数中通过向 信号处理线程(signal_thread,在上面刚讨论的) 发送MYSQL_KILL_SIGNAL。

这个是MYSQL_KILL_SIGNAL的宏定义。
根据上面的讨论,信号处理线程会开始关闭mysqld。