一、MySql中的线程创建方式
为了跨平台主要支持WIN和LINUX,在创建的源码中也可以看这两种创建的方式,在源码中可以清晰的看到区别这两的方式仍然使用的经典的宏判断。其实线程落到这里,基本上就回到了传统的线程处理的整个过程,只是看开发者对其是否进行二次封装和抽象。下面看一下相关的代码分析。
二、具体源码
从上一篇add_connection这个函数开始看:
bool Per_thread_connection_handler::add_connection(Channel_info *channel_info) {
int error = 0;
my_thread_handle id;
DBUG_TRACE;
// Simulate thread creation for test case before we check thread cache
DBUG_EXECUTE_IF("fail_thread_create", error = 1; goto handle_error;);
if (!check_idle_thread_and_enqueue_connection(channel_info)) return false;
/*
There are no idle threads avaliable to take up the new
connection. Create a new thread to handle the connection
*/
channel_info->set_prior_thr_create_utime();
error =
mysql_thread_create(key_thread_one_connection, &id, &connection_attrib,
handle_connection, (void *)channel_info);
#ifndef NDEBUG
handle_error:
#endif // !NDEBUG
if (error) {
connection_errors_internal++;
if (!create_thd_err_log_throttle.log())
LogErr(ERROR_LEVEL, ER_CONN_PER_THREAD_NO_THREAD, error);
channel_info->send_error_and_close_channel(ER_CANT_CREATE_THREAD, error,
true);
Connection_handler_manager::dec_connection_count();
return true;
}
Global_THD_manager::get_instance()->inc_thread_created();
DBUG_PRINT("info", ("Thread created"));
return false;
}
首先检查一下有没有空闲的线程,如果有就返回一个False的值,表明有空闲的线程可用并唤醒他:
bool Per_thread_connection_handler::check_idle_thread_and_enqueue_connection(
Channel_info *channel_info) {
bool res = true;
mysql_mutex_lock(&LOCK_thread_cache);
if (Per_thread_connection_handler::blocked_pthread_count > wake_pthread) {
DBUG_PRINT("info", ("waiting_channel_info_list->push %p", channel_info));
waiting_channel_info_list->push_back(channel_info);
wake_pthread++;
mysql_cond_signal(&COND_thread_cache);
res = false;
}
mysql_mutex_unlock(&LOCK_thread_cache);
return res;
}
如果没有了空闲线程就创建一个新的线程:
#define mysql_thread_create(K, P1, P2, P3, P4) \
inline_mysql_thread_create(K, P1, P2, P3, P4)
static inline int inline_mysql_thread_create(
PSI_thread_key key MY_ATTRIBUTE((unused)), my_thread_handle *thread,
const my_thread_attr_t *attr, my_start_routine start_routine, void *arg) {
int result;
#ifdef HAVE_PSI_THREAD_INTERFACE
result = PSI_THREAD_CALL(spawn_thread)(key, thread, attr, start_routine, arg);
#else
result = my_thread_create(thread, attr, start_routine, arg);
#endif
return result;
}
这里使用两种方法创建线程,一个是PSI_THREAD_CALL这个宏产生的:
/**
Spawn a thread.
This method creates a new thread, with instrumentation.
@param key the instrumentation key for this thread
@param thread the resulting thread
@param attr the thread attributes
@param start_routine the thread start routine
@param arg the thread start routine argument
*/
typedef int (*spawn_thread_v1_t)(PSI_thread_key key, my_thread_handle *thread,
const my_thread_attr_t *attr,
void *(*start_routine)(void *), void *arg);
PSI是performance schema interface的缩写,表示拥有各种安全检测的环境机制的声明和初始化。
另外一个是:
int my_thread_create(my_thread_handle *thread, const my_thread_attr_t *attr,
my_start_routine func, void *arg) {
#ifndef _WIN32
return pthread_create(&thread->thread, attr, func, arg);
#else
struct thread_start_parameter *par;
unsigned int stack_size;
par = (struct thread_start_parameter *)malloc(sizeof(*par));
if (!par) goto error_return;
par->func = func;
par->arg = arg;
stack_size = attr ? attr->dwStackSize : 0;
thread->handle =
(HANDLE)_beginthreadex(NULL, stack_size, win_thread_start, par, 0,
(unsigned int *)&thread->thread);
if (thread->handle) {
/* Note that JOINABLE is default, so attr == NULL => JOINABLE. */
if (attr && attr->detachstate == MY_THREAD_CREATE_DETACHED) {
/*
Close handles for detached threads right away to avoid leaking
handles. For joinable threads we need the handle during
my_thread_join. It will be closed there.
*/
CloseHandle(thread->handle);
thread->handle = NULL;
}
return 0;
}
my_osmaperr(GetLastError());
free(par);
error_return:
thread->thread = 0;
thread->handle = NULL;
return 1;
#endif
}
这其中又通过宏 _WIN32来判断是否是WINDOWS平台,来使用不同的OS的接口创建线程,特别需要提醒的是,在WINDOWS上使用的_beginthreadex这个API,有兴趣的可以看一看在Windows平台上创建线程的几种方法中,各种的目的和优势以及如何应用。
在mysys/my_thread.cc中,有几个线程相关的属性封装。如:
int my_thread_join(my_thread_handle *thread, void **value_ptr) {
#ifndef _WIN32
return pthread_join(thread->thread, value_ptr);
#else
DWORD ret;
int result = 0;
ret = WaitForSingleObject(thread->handle, INFINITE);
if (ret != WAIT_OBJECT_0) {
my_osmaperr(GetLastError());
result = 1;
}
if (thread->handle) CloseHandle(thread->handle);
thread->thread = 0;
thread->handle = NULL;
return result;
#endif
}
int my_thread_cancel(my_thread_handle *thread) {
#ifndef _WIN32
return pthread_cancel(thread->thread);
#else
bool ok = false;
if (thread->handle) {
ok = TerminateThread(thread->handle, 0);
CloseHandle(thread->handle);
}
if (ok) return 0;
errno = EINVAL;
return -1;
#endif
}
void my_thread_exit(void *value_ptr) {
#ifndef _WIN32
pthread_exit(value_ptr);
#else
_endthreadex(0);
#endif
}
而在my_thr_init.cc中则有大量的线程初始化相关的函数:
bool my_thread_global_init() {
if (my_thread_global_init_done) return false;
my_thread_global_init_done = true;
#if defined(SAFE_MUTEX)
safe_mutex_global_init(); /* Must be called early */
#endif
#ifdef PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP
/*
Set mutex type to "fast" a.k.a "adaptive"
In this case the thread may steal the mutex from some other thread
that is waiting for the same mutex. This will save us some
context switches but may cause a thread to 'starve forever' while
waiting for the mutex (not likely if the code within the mutex is
short).
*/
pthread_mutexattr_init(&my_fast_mutexattr);
pthread_mutexattr_settype(&my_fast_mutexattr, PTHREAD_MUTEX_ADAPTIVE_NP);
#endif
#ifdef PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP
/*
Set mutex type to "errorcheck"
*/
pthread_mutexattr_init(&my_errorcheck_mutexattr);
pthread_mutexattr_settype(&my_errorcheck_mutexattr, PTHREAD_MUTEX_ERRORCHECK);
#endif
mysql_mutex_init(key_THR_LOCK_malloc, &THR_LOCK_malloc, MY_MUTEX_INIT_FAST);
mysql_mutex_init(key_THR_LOCK_open, &THR_LOCK_open, MY_MUTEX_INIT_FAST);
mysql_mutex_init(key_THR_LOCK_charset, &THR_LOCK_charset, MY_MUTEX_INIT_FAST);
mysql_mutex_init(key_THR_LOCK_lock, &THR_LOCK_lock, MY_MUTEX_INIT_FAST);
mysql_mutex_init(key_THR_LOCK_myisam, &THR_LOCK_myisam, MY_MUTEX_INIT_SLOW);
mysql_mutex_init(key_THR_LOCK_myisam_mmap, &THR_LOCK_myisam_mmap,
MY_MUTEX_INIT_FAST);
mysql_mutex_init(key_THR_LOCK_heap, &THR_LOCK_heap, MY_MUTEX_INIT_FAST);
mysql_mutex_init(key_THR_LOCK_net, &THR_LOCK_net, MY_MUTEX_INIT_FAST);
#ifndef NDEBUG
mysql_mutex_init(key_THR_LOCK_threads, &THR_LOCK_threads, MY_MUTEX_INIT_FAST);
mysql_cond_init(key_THR_COND_threads, &THR_COND_threads);
#endif
return false;
}
void my_thread_global_end() {
#ifndef NDEBUG
struct timespec abstime;
bool all_threads_killed = true;
set_timespec(&abstime, my_thread_end_wait_time);
mysql_mutex_lock(&THR_LOCK_threads);
while (THR_thread_count > 0) {
int error =
mysql_cond_timedwait(&THR_COND_threads, &THR_LOCK_threads, &abstime);
if (is_timeout(error)) {
#ifndef _WIN32
/*
We shouldn't give an error here, because if we don't have
pthread_kill(), programs like mysqld can't ensure that all threads
are killed when we enter here.
*/
if (THR_thread_count) /* purecov: begin inspected */
my_message_local(ERROR_LEVEL, EE_FAILED_TO_KILL_ALL_THREADS,
THR_thread_count);
/* purecov: end */
#endif
all_threads_killed = false;
break;
}
}
mysql_mutex_unlock(&THR_LOCK_threads);
#endif
#ifdef PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP
pthread_mutexattr_destroy(&my_fast_mutexattr);
#endif
#ifdef PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP
pthread_mutexattr_destroy(&my_errorcheck_mutexattr);
#endif
mysql_mutex_destroy(&THR_LOCK_malloc);
mysql_mutex_destroy(&THR_LOCK_open);
mysql_mutex_destroy(&THR_LOCK_lock);
mysql_mutex_destroy(&THR_LOCK_myisam);
mysql_mutex_destroy(&THR_LOCK_myisam_mmap);
mysql_mutex_destroy(&THR_LOCK_heap);
mysql_mutex_destroy(&THR_LOCK_net);
mysql_mutex_destroy(&THR_LOCK_charset);
#ifndef NDEBUG
if (all_threads_killed) {
mysql_mutex_destroy(&THR_LOCK_threads);
mysql_cond_destroy(&THR_COND_threads);
}
#endif
my_thread_global_init_done = false;
}
/**
Allocate thread specific memory for the thread, used by mysys and dbug
@note This function may called multiple times for a thread, for example
if one uses my_init() followed by mysql_server_init().
@retval false ok
@retval true Fatal error; mysys/dbug functions can't be used
*/
extern "C" bool my_thread_init() {
#ifndef NDEBUG
struct st_my_thread_var *tmp;
#endif
if (!my_thread_global_init_done)
return true; /* cannot proceed with unintialized library */
#ifdef _WIN32
install_sigabrt_handler();
#endif
#ifndef NDEBUG
if (mysys_thread_var()) return false;
if (!(tmp = (struct st_my_thread_var *)calloc(1, sizeof(*tmp)))) return true;
mysql_mutex_lock(&THR_LOCK_threads);
tmp->id = ++thread_id;
++THR_thread_count;
mysql_mutex_unlock(&THR_LOCK_threads);
set_mysys_thread_var(tmp);
#endif
return false;
}
在mysys目录下还有大量的相关线程代码的封装文件,如thr_lock.cc等,就不再一一分析。
最后再看一下MYSQL中对线程的封装处理:
struct PFS_spawn_thread_arg {
ulonglong m_thread_internal_id;
char m_username[USERNAME_LENGTH];
uint m_username_length;
char m_hostname[HOSTNAME_LENGTH];
uint m_hostname_length;
PSI_thread_key m_child_key;
const void *m_child_identity;
void *(*m_user_start_routine)(void *);
void *m_user_arg;
};
extern "C" {
static void *pfs_spawn_thread(void *arg) {
PFS_spawn_thread_arg *typed_arg = (PFS_spawn_thread_arg *)arg;
void *user_arg;
void *(*user_start_routine)(void *);
PFS_thread *pfs;
/* First, attach instrumentation to this newly created pthread. */
PFS_thread_class *klass = find_thread_class(typed_arg->m_child_key);
if (likely(klass != nullptr)) {
pfs = create_thread(klass, typed_arg->m_child_identity, 0);
if (likely(pfs != nullptr)) {
pfs->m_thread_os_id = my_thread_os_id();
clear_thread_account(pfs);
pfs->m_parent_thread_internal_id = typed_arg->m_thread_internal_id;
memcpy(pfs->m_username, typed_arg->m_username, sizeof(pfs->m_username));
pfs->m_username_length = typed_arg->m_username_length;
memcpy(pfs->m_hostname, typed_arg->m_hostname, sizeof(pfs->m_hostname));
pfs->m_hostname_length = typed_arg->m_hostname_length;
set_thread_account(pfs);
}
} else {
pfs = nullptr;
}
my_thread_set_THR_PFS(pfs);
pfs_notify_thread_create((PSI_thread *)pfs);
/*
Secondly, free the memory allocated in spawn_thread_v1().
It is preferable to do this before invoking the user
routine, to avoid memory leaks at shutdown, in case
the server exits without waiting for this thread.
*/
user_start_routine = typed_arg->m_user_start_routine;
user_arg = typed_arg->m_user_arg;
my_free(typed_arg);
/* Then, execute the user code for this thread. */
(*user_start_routine)(user_arg);
return nullptr;
}
} // extern "C"
/**
Implementation of the thread instrumentation interface.
@sa PSI_v2::spawn_thread.
*/
int pfs_spawn_thread_vc(PSI_thread_key key, my_thread_handle *thread,
const my_thread_attr_t *attr,
void *(*start_routine)(void *), void *arg) {
PFS_spawn_thread_arg *psi_arg;
PFS_thread *parent;
/* psi_arg can not be global, and can not be a local variable. */
psi_arg = (PFS_spawn_thread_arg *)my_malloc(
PSI_NOT_INSTRUMENTED, sizeof(PFS_spawn_thread_arg), MYF(MY_WME));
if (unlikely(psi_arg == nullptr)) {
return EAGAIN;
}
psi_arg->m_child_key = key;
psi_arg->m_child_identity = (arg ? arg : thread);
psi_arg->m_user_start_routine = start_routine;
psi_arg->m_user_arg = arg;
parent = my_thread_get_THR_PFS();
if (parent != nullptr) {
/*
Make a copy of the parent attributes.
This is required, because instrumentation for this thread (the parent)
may be destroyed before the child thread instrumentation is created.
*/
psi_arg->m_thread_internal_id = parent->m_thread_internal_id;
memcpy(psi_arg->m_username, parent->m_username,
sizeof(psi_arg->m_username));
psi_arg->m_username_length = parent->m_username_length;
memcpy(psi_arg->m_hostname, parent->m_hostname,
sizeof(psi_arg->m_hostname));
psi_arg->m_hostname_length = parent->m_hostname_length;
} else {
psi_arg->m_thread_internal_id = 0;
psi_arg->m_username_length = 0;
psi_arg->m_hostname_length = 0;
}
int result = my_thread_create(thread, attr, pfs_spawn_thread, psi_arg);
if (unlikely(result != 0)) {
my_free(psi_arg);
}
return result;
}
这里面用到了一个很大的对象:
PSI_thread_service_v4 pfs_thread_service_v4 = {
/* Old interface, for plugins. */
pfs_register_thread_vc,
pfs_spawn_thread_vc,
pfs_new_thread_vc,
pfs_set_thread_id_vc,
pfs_get_current_thread_internal_id_vc,
pfs_get_thread_internal_id_vc,
pfs_get_thread_by_id_vc,
pfs_set_thread_THD_vc,
pfs_set_thread_os_id_vc,
pfs_get_thread_vc,
pfs_set_thread_user_vc,
pfs_set_thread_account_vc,
pfs_set_thread_db_vc,
pfs_set_thread_command_vc,
pfs_set_connection_type_vc,
pfs_set_thread_start_time_vc,
pfs_set_thread_info_vc,
pfs_set_thread_resource_group_vc,
pfs_set_thread_resource_group_by_id_vc,
pfs_set_thread_vc,
pfs_set_thread_peer_port_vc,
pfs_aggregate_thread_status_vc,
pfs_delete_current_thread_vc,
pfs_delete_thread_vc,
pfs_set_thread_connect_attrs_vc,
pfs_get_current_thread_event_id_vc,
pfs_get_thread_event_id_vc,
pfs_get_thread_system_attrs_vc,
pfs_get_thread_system_attrs_by_id_vc,
pfs_register_notification_vc,
pfs_unregister_notification_vc,
pfs_notify_session_connect_vc,
pfs_notify_session_disconnect_vc,
pfs_notify_session_change_user_vc};
其实会发现,最后还是会调用 my_thread_create,回到了上面的老路,也就是说马甲换了多又多,但仍然得知道马甲下是什么,特别是在马甲中揣了啥东西,这样就会把相关的流程搞清楚明白。更多的说明可以查看官网的相关资料:
https://dev.mysql.com/doc/dev/mysql-server/latest/group__psi__abi__thread.html
三、总结
MySql源码的版本更新的比较快,网上很多的代码都是老的代码分析,要多比较多看,细节上有不少变化。特别是一些平台上的具体封装以及一些版本的不同处理,都需要认真分析,线程用的地方非常多,会在后面遇到相关细节后再依相关的需要展开分析。
时不我待,失不再来!不断努力!