mysql源码解读 — 线程的具体创建

一、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源码的版本更新的比较快,网上很多的代码都是老的代码分析,要多比较多看,细节上有不少变化。特别是一些平台上的具体封装以及一些版本的不同处理,都需要认真分析,线程用的地方非常多,会在后面遇到相关细节后再依相关的需要展开分析。
时不我待,失不再来!不断努力!
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
可以使用Java多线程创建MySQL连接池,以下是一个简单的实现示例: ```java import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class MySQLConnectionPool { private String url; private String username; private String password; private int initialSize; private int maxActive; private List<Connection> pool; public MySQLConnectionPool(String url, String username, String password, int initialSize, int maxActive) { this.url = url; this.username = username; this.password = password; this.initialSize = initialSize; this.maxActive = maxActive; this.pool = new ArrayList<>(maxActive); init(); } private void init() { try { for (int i = 0; i < initialSize; i++) { Connection conn = DriverManager.getConnection(url, username, password); pool.add(conn); } } catch (SQLException e) { e.printStackTrace(); } } public synchronized Connection getConnection() throws SQLException { if (pool.isEmpty()) { if (pool.size() < maxActive) { Connection conn = DriverManager.getConnection(url, username, password); pool.add(conn); } else { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } return pool.remove(0); } public synchronized void releaseConnection(Connection conn) { pool.add(conn); notifyAll(); } } ``` 在这个示例中,我们使用一个线程安全的 List 来保存连接池中的连接。在初始化时,我们创建 initialSize 个连接,并将它们添加到连接池中。在 getConnection() 方法中,我们检查连接池是否为空,如果是,则创建新的连接并将其添加到连接池中(但不超过 maxActive)。如果连接池不为空,则返回连接池中的第一个连接。在 releaseConnection() 方法中,我们将连接返回到连接池中,并唤醒等待线程。 需要注意的是,这只是一个简单的示例,实际使用时需要考虑连接池的线程安全性和性能等问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值