源码阅读系列——进阶篇(Android的线程分析)你是否真的了解线程_luoluofeixia的博客-程序员秘密

技术标签: java  android  

1、线程概念

1.1 进程与线程

我们知道,在linux系统里,有进程和线程的概念。进程是linux最小的资源管理单位,而线程是最小的执行单元。进程与线程不一样,他拥有自己独立的内存空间,而线程则是共享进程的内存空间。所以,相对而言,会更好的利用资源。
当然,学习过linux内核的人也知道,不管是线程还是进程,在内核中都理解为进程,他们都有Task_Struct结构进行描述。这是因为linux内核本身在cpu调度的时候,进程为最小单元,所以线程在内核中表现为一个轻量化的进程,所以我们也就知道线程的切换,实际上就是内核的进程管理器进行的切换操作,而相应的线程的优先级,同样就取决于在内核中的nice值和priority设置。而相应的用户态下线程的管理本身,则是通过进程首次建立就自动创建的管理线程进行管理,例如线程的停止,线程的创建。
通常我们在进程中创建线程,会调用pthread_create方法,这个方法会调用到_clone方法,这个跟fork()进程时调用的_fork()方法最终都会调用到do_fork()方法,而作为进程和线程定义上的差别,do_fork的参数当然有所区分。
比如我们通过do_fork创建线程时,传入进程参数,包括内存空间(堆、栈、全局变量、数据区等)、文件系统信息、文件描述符表、信号句柄表、进程ID(当前进程id、父进程id)进行共享,并让每个线程仍然保持自己独立的运行栈。这点不同于创建进程,概念上子进程则拥有独立共享内存空间、文件系统空间、信号句柄表和进程ID,当然实际实现的时候,linux使用了一种机制,在父进程fork子进程,会共享整个自己的内存空间,如果子进程只是读取,那么则是共享父进程的空,一旦修改内存中值的时候,父进程会复制一份虚拟地址空间,指向父进程的物理空间,这样就成为完全独立的子进程的内存空间,这也就是我们常说的copy-on-write写时复制,当然父子进程也存在共享的内容,一个是文件描述符表(比如父进程建立socket连接的fd,子进程也会共享,他只是在这个fd上计数+1,这时候,子进程如果用不到,就就需要关闭socket的fd,因为fd只有在计数为0的时候,才会真正关闭,所以这一步必不可少),以及mmap的映射关系。所以这一点与线程的共享存在本质的区别。

2、查看线程进程信息

2.1 进程信息

以Android设备为例,我们可以通过两种方式查看进程信息:
adb shell ps
可以看到类似如下的信息:

USER           PID  PPID     VSZ    RSS WCHAN            ADDR S NAME  
u0_a9        27576   239 1139076  22880 SyS_epoll_wait a7c4c1b8 S android.process.media

其中:
user 表示用户信息,在android里面,u0表示主用户,u10\u20\u30等就代表副用户,后面的数字,就表示userid,u0的userid就是0,当然他们在应用的uid里面也承担信息,比如u0表示的就表示10000,u10就表示110000,依次类推,而u0_a9在android里面就表示10000 + 0xa9 = 10169。一个应用安装之后的uid是不变的。

PID 就表示进程号。跟linux里面一样表示进程信息。

PPID 就表示该进程的父进程号,如果看过ActivityManagerService的源码,可以知道,一般android应用,都是通过zygote孵化的进程,所以这个上面的239一般就表示zygote进程(同样zygote则是由init进程初始化,android初始化进程)。如下:

root           239     1 1055008   9568 poll_schedule_timeout a7c4c390 S zygote

root             1     0   18596   1700 SyS_epoll_wait  b5e34 S init

所以通过类似这样的手段,可以找到进程的关系。

除了ps的指令,我们也可以通过linux的伪文件系统,查看进程信息
adb shell ls -al /proc
实际上,运行的进程信息,全部都在/proc目录下。

2.2 线程信息

同样ps也提供的方法:
例如我们查看进程12424的所有线程。
adb shell ps -T | grep 12424
就会列出所有的线程信息,其中-T 表示列出线程信息。

当然/proc里面包含了进程的信息,同样也包含了线程信息,我们可以通过查看:
adb shell ls -al /proc/12424 查看到所有的线程信息
当然我们在定位线程泄漏问题的时候,也可以通过以下方式查看线程的数量,以判断是否不断增长:
adb shell ls /proc/12424/task | wc -l。

3、Android线程

有了以上的基础解析,对线程有了初步的理解,我们正式阅读java源码,Java有很多的线程实现方式,比如Thread,Runnable、Callable、Future、FutureTask等,那这些类如何连接起来,如何实现的呢?我们先从Thread说起。

3.1 Thread的创建和运行

因为线程这块涉及到VM调用底层的机制,android的art这块代码,是区别与Java线程代码的,但是,基本的思路类似,区别仅仅是调用native的vm代码上存在区别,上层基本一样,以start的为例:

// JAVA代码
public synchronized void start() {
    
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        group.add(this);
        boolean started = false;
        try {
    
            start0();
            started = true;
        } finally {
    
            try {
    
                if (!started) {
    
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
    
            }
        }
    }

Android 代码

public synchronized void start() {
    
    if (threadStatus != 0 || started)
        throw new IllegalThreadStateException();
    started = false;
    try {
    
        nativeCreate(this, stackSize, daemon);
        started = true;
    } finally {
    
        try {
    
            if (!started) {
    
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
    
        }
    }
}

所以看到只是start0()和nativeCreate()两个native方法有区别,我们这边以Android的线程的方法进行分析。这个代码可以在SDK代码里找到,也可以参见
http://androidxref.com/8.1.0_r33/xref/art/runtime/native/java_lang_Thread.cc

static void Thread_nativeCreate(JNIEnv* env, jclass, jobject java_thread, jlong stack_size, jboolean daemon) {
    
  Runtime* runtime = Runtime::Current();
  if (runtime->IsZygote() && runtime->IsZygoteNoThreadSection()) {
      // 判断是否为zygote进程,并且是zygote不可建立线程的部分,则抛出异常
    jclass internal_error = env->FindClass("java/lang/InternalError");
    CHECK(internal_error != nullptr);
    env->ThrowNew(internal_error, "Cannot create threads in zygote");
    return;
  }

  Thread::CreateNativeThread(env, java_thread, stack_size, daemon == JNI_TRUE); // 创建线程
}

我们继续看线程创建的函数:

void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) {
    
  CHECK(java_peer != nullptr);
  Thread* self = static_cast<JNIEnvExt*>(env)->GetSelf();

  if (VLOG_IS_ON(threads)) {
     // 判断线程的日志是否打开,打开则给每个Thread命名
    ScopedObjectAccess soa(env);

    ArtField* f = jni::DecodeArtField(WellKnownClasses::java_lang_Thread_name);
    ObjPtr<mirror::String> java_name =
        f->GetObject(soa.Decode<mirror::Object>(java_peer))->AsString();
    std::string thread_name;
    if (java_name != nullptr) {
    
      thread_name = java_name->ToModifiedUtf8();
    } else {
    
      thread_name = "(Unnamed)";
    }

    VLOG(threads) << "Creating native thread for " << thread_name;
    self->Dump(LOG_STREAM(INFO));
  }
  Runtime* runtime = Runtime::Current();
  bool thread_start_during_shutdown = false;
  {
    
    MutexLock mu(self, *Locks::runtime_shutdown_lock_);
    if (runtime->IsShuttingDownLocked()) {
    
      thread_start_during_shutdown = true;  // 判断当前是否刚好是VM关闭的场景
    } else {
    
      runtime->StartThreadBirth(); // 实际就是记录线程创建数+1
    }
  }
  if (thread_start_during_shutdown) {
     // 如果在VM关闭过程中新建,抛异常
    ScopedLocalRef<jclass> error_class(env, env->FindClass("java/lang/InternalError"));
    env->ThrowNew(error_class.get(), "Thread starting during runtime shutdown");  
    return;
  }

  Thread* child_thread = new Thread(is_daemon);
  child_thread->tlsPtr_.jpeer = env->NewGlobalRef(java_peer);
  stack_size = FixStackSize(stack_size); // 设置运行栈的大小
  env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer,
                    reinterpret_cast<jlong>(child_thread));
  std::string error_msg;
  std::unique_ptr<JNIEnvExt> child_jni_env_ext(
      JNIEnvExt::Create(child_thread, Runtime::Current()->GetJavaVM(), &error_msg));

  int pthread_create_result = 0;
  if (child_jni_env_ext.get() != nullptr) {
    
    pthread_t new_pthread;
    pthread_attr_t attr;
    child_thread->tlsPtr_.tmp_jni_env = child_jni_env_ext.get();
    CHECK_PTHREAD_CALL(pthread_attr_init, (&attr), "new thread");
    CHECK_PTHREAD_CALL(pthread_attr_setdetachstate, (&attr, PTHREAD_CREATE_DETACHED),
                       "PTHREAD_CREATE_DETACHED");
    CHECK_PTHREAD_CALL(pthread_attr_setstacksize, (&attr, stack_size), stack_size);
    // 创建线程,并创建callback用于线程创建完成回调,回调见后面的代码
    pthread_create_result = pthread_create(&new_pthread,
                                           &attr,
                                           Thread::CreateCallback,
                                           child_thread);
    CHECK_PTHREAD_CALL(pthread_attr_destroy, (&attr), "new thread");

    if (pthread_create_result == 0) {
    
      child_jni_env_ext.release();
      return;
    }
  }
  {
    
    MutexLock mu(self, *Locks::runtime_shutdown_lock_);
    runtime->EndThreadBirth(); // 当前线程完成创建,如果发现现在没有线程创建,并且VM开始shutdown了,就返回告知退出线程。
  }
  // 删除掉全局的引用,因为此时init还没有运行。
  env->DeleteGlobalRef(child_thread->tlsPtr_.jpeer);
  child_thread->tlsPtr_.jpeer = nullptr;
  delete child_thread;
  child_thread = nullptr;
  env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer, 0);
  {
    
    std::string msg(child_jni_env_ext.get() == nullptr ?
        StringPrintf("Could not allocate JNI Env: %s", error_msg.c_str()) :
        StringPrintf("pthread_create (%s stack) failed: %s",
PrettySize(stack_size).c_str(), strerror(pthread_create_result)));
    ScopedObjectAccess soa(env);
    soa.Self()->ThrowOutOfMemoryError(msg.c_str());
  }
}

我们看一下回调之后做了啥

void* Thread::CreateCallback(void* arg) {
    
  Thread* self = reinterpret_cast<Thread*>(arg);
  Runtime* runtime = Runtime::Current();
  if (runtime == nullptr) {
    
    LOG(ERROR) << "Thread attaching to non-existent runtime: " << *self;
    return nullptr;
  }
  {
    
    // 加锁,直到完成了thread的init操作
    MutexLock mu(nullptr, *Locks::runtime_shutdown_lock_);
    // 检查是否VM shut down
    CHECK(!runtime->IsShuttingDownLocked());
    // 对Thread进行init操作
    CHECK(self->Init(runtime->GetThreadList(), runtime->GetJavaVM(), self->tlsPtr_.tmp_jni_env));
    self->tlsPtr_.tmp_jni_env = nullptr;
    Runtime::Current()->EndThreadBirth();
  }
  {
    
    ScopedObjectAccess soa(self);
    self->InitStringEntryPoints();

    // Copy peer into self, deleting global reference when done.
    CHECK(self->tlsPtr_.jpeer != nullptr);
    self->tlsPtr_.opeer = soa.Decode<mirror::Object>(self->tlsPtr_.jpeer).Ptr();
    self->GetJniEnv()->DeleteGlobalRef(self->tlsPtr_.jpeer);
    self->tlsPtr_.jpeer = nullptr;
    self->SetThreadName(self->GetThreadName()->ToModifiedUtf8().c_str());

    ArtField* priorityField = jni::DecodeArtField(WellKnownClasses::java_lang_Thread_priority);
    // 设置线程真实的优先级
    self->SetNativePriority(priorityField->GetInt(self->tlsPtr_.opeer));

    // 告知VM线程的创建
    runtime->GetRuntimeCallbacks()->ThreadStart(self);

    // 调用Java层Thread的run方法
    ObjPtr<mirror::Object> receiver = self->tlsPtr_.opeer;
    jmethodID mid = WellKnownClasses::java_lang_Thread_run;
    ScopedLocalRef<jobject> ref(soa.Env(), soa.AddLocalReference<jobject>(receiver));
    InvokeVirtualOrInterfaceWithJValues(soa, ref.get(), mid, nullptr);
  }
  // 将自己从线程列表中删除
  Runtime::Current()->GetThreadList()->Unregister(self);

  return nullptr;
}

通过这样的过程,我们知道线程的创建还是调用pthread_create方法。并且为了保证VM堆线程可控,也会告知VM(也就是art)线程情况,我们看一下runtime->GetRuntimeCallbacks()->ThreadStart实现:

void RuntimeCallbacks::AddThreadLifecycleCallback(ThreadLifecycleCallback* cb) {
    
  thread_callbacks_.push_back(cb);
}
void RuntimeCallbacks::ThreadStart(Thread* self) {
    
  for (ThreadLifecycleCallback* cb : thread_callbacks_) {
    
    cb->ThreadStart(self);
  }
}

我们看一下AddThreadLifecycleCallback的调用处,此代码在runtime.cc中:

callbacks_->AddThreadLifecycleCallback(Dbg::GetThreadLifecycleCallback());

实际上回调回来,只是为了Dbg(也就是debugger工具)获取当前的线程状况。

3.2 线程组

3.2.1 线程组概念

什么是线程组,从概念上理解很简单,就是将线程组合成一个集合,这个集合就是线程组,线程组可以包含线程,也可以嵌套线程组,可以理解成一种树状的结构。我们看一下线程组的定义:

public ThreadGroup(String name) {
    
    this(Thread.currentThread().getThreadGroup(), name);
}
public ThreadGroup(ThreadGroup parent, String name) {
    
    this(checkParentAccess(parent), parent, name);
}
private ThreadGroup(Void unused, ThreadGroup parent, String name) {
    
    this.name = name;
    this.maxPriority = parent.maxPriority;
    this.daemon = parent.daemon;
    this.vmAllowSuspension = parent.vmAllowSuspension;
    this.parent = parent;
    parent.add(this);
}

从这个方法中,我们知道,如果创建ThreadGroup时,没有设置父ThreadGroup,则以当前线程的线程组作为此线程组的父线程组。并且默认情况下线程组的优先级会设置成最大,这个线程组的优先级也会影响到线程的优先级(后面我们会讲到)。
我们看一下线程组在线程上的应用,我们看一下构造函数:

public Thread() {
    
    init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(ThreadGroup group, Runnable target) {
    
    init(group, target, "Thread-" + nextThreadNum(), 0);
}

也就是说,Thread可以让调用者主动设置ThreadGroup,同样也可使设置成null,我们知道线程一定是有线程组的,我们看一下如果设置成null的实现:

    private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
    
        Thread parent = currentThread();
        if (g == null) {
    
            g = parent.getThreadGroup();
        }
   }

也就是说,如果没有设置线程组,则以当前运行线程(也就是创建当前线程的父线程)的ThreadGroup作为当前要创建的线程的ThreadGroup。这一点与我们之前讲到主动创建ThreadGroup相似却不一样,主动创建ThreadGroup,是将当前运行线程的线程组作为当前创建线程组的父线程组;而创建线程时,则是设置当前运行线程的线程组为当前要创建线程的线程组,一个是父子关系,一个是公用父亲的关系。
我们在实际使用时,一般都是不手动创建线程组的。

3.2.2 线程组作用

有了线程组的概念后,那么线程组有啥作用,个人认为有两点:
1、因为当前线程持有其线程组,可以通过如下方法获取到线程组:

public final ThreadGroup getThreadGroup() {
    
    // Android-changed: Return null if the thread is terminated.
    if (getState() == Thread.State.TERMINATED) {
    
        return null;
    }
    return group;
}

我们可以获取到同组线程的一些信息,例如
a、因为ThreadGroup在线程创建的过程中,thread也会告知ThreadGroup自己的运行状态,所以就可以获取到如下:
activeGroupCount() 当前活跃线程组、activeCount()当前活跃线程。
b、调用线程组的某个thread的UncaughtException
public void uncaughtException(Thread t, Throwable e) {

    if (parent != null) {
    
        parent.uncaughtException(t, e);
    } else {
    
        Thread.UncaughtExceptionHandler ueh =
            Thread.getDefaultUncaughtExceptionHandler();
        if (ueh != null) {
    
            ueh.uncaughtException(t, e);
        } else if (!(e instanceof ThreadDeath)) {
    
            System.err.print("Exception in thread \""
                             + t.getName() + "\" ");
            e.printStackTrace(System.err);
        }
    }
}

2、通过线程组统一设置的线程优先级对线程进行统一约束,结论先抛出,线程的优先级不高于线程组的优先级。

当然除了以上的两个作用,还有一个更重要的作用,就是协助排查问题,因为有了线程组的关系,我们在分析trace的时候,可以根据场景得到很多线程是由谁创建的。比如我们常见的trace中的group=main,基本上都是有主线程创建的线程,除非被创建的线程主动设置了线程组,那我们一样可以通过线程组的父线程组得到关联关系。

3.2.3 主线程组的概念

讲到这,一定有同学有个疑问,为什么主进程的group=main,name=main,如果阅读过zygote启动虚拟机流程的同学,一定知道进程启动的时候,调用art的源码会走到runtime.cc的

bool Runtime::Create(RuntimeArgumentMap&& runtime_options) {
    
  if (!instance_->Init(std::move(runtime_options))) {
    
    instance_ = nullptr;
    return false;
  }
  return true;
}

继而调用到init方法,我们主要看线程相关的实现:

bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) {
    
     Thread::Startup();
     Thread* self = Thread::Attach("main", false, nullptr, false);
     self->TransitionFromSuspendedToRunnable();
}

Startup主要实现创建线程的TLS(线程局部存储)

CHECK_PTHREAD_CALL(pthread_key_create, (&Thread::pthread_key_self_, Thread::ThreadExitCallback),
                     "self key");

Attach实际就是创建Thread线程并进行初始化的过程,可以看到设置了线程名为main线程,也就是主线程。
执行完Runtime::Create之后,就是执行Runtime::Start(),我们同样看一下主要实现:

bool Runtime::Start() {
    
     Thread* self = Thread::Current();
     InitThreadGroups(self);
     Thread::FinishStartup();
}

第一句代码:获取当前的主线程
第二句:

void Runtime::InitThreadGroups(Thread* self) {
    
  JNIEnvExt* env = self->GetJniEnv();
  ScopedJniEnvLocalRefState env_state(env);
  main_thread_group_ =
      env->NewGlobalRef(env->GetStaticObjectField(
          WellKnownClasses::java_lang_ThreadGroup,
          WellKnownClasses::java_lang_ThreadGroup_mainThreadGroup));
  system_thread_group_ =
      env->NewGlobalRef(env->GetStaticObjectField(
          WellKnownClasses::java_lang_ThreadGroup,
          WellKnownClasses::java_lang_ThreadGroup_systemThreadGroup));
}

这个java_lang_ThreadGroup_mainThreadGroup实际上就是反射调用到java层的实现,对应的java层代码就是ThreadGroup.java的:

static final ThreadGroup mainThreadGroup = new ThreadGroup(systemThreadGroup, "main");

所以这一步,就是创建mainThreadGroup和systemThreadGroup。
第三句,group获取了,我们最后再把group设置给主线程,就是通过FinishStartup方法:

void Thread::FinishStartup() {
    
  Runtime* runtime = Runtime::Current();

  ScopedObjectAccess soa(Thread::Current());
  Thread::Current()->CreatePeer("main", false, runtime->GetMainThreadGroup());
  Thread::Current()->AssertNoPendingException();

  Runtime::Current()->GetClassLinker()->RunRootClinits();
}

主要涉及实现在CreatePeer中,我们看到执行:

  env->CallNonvirtualVoidMethod(peer.get(),
                                WellKnownClasses::java_lang_Thread,
                                WellKnownClasses::java_lang_Thread_init,
                                thread_group, thread_name.get(), thread_priority, thread_is_daemon);

经过这样的几个过程,我们创建了主线程,并且将主线程的线程组都设置成main。

3.3 线程优先级

Thread.java的源码中有优先级priority的设置

 public final void setPriority(int newPriority) {
    
        ThreadGroup g;
        checkAccess();
        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
    
            // Android-changed: Improve exception message when the new priority
            // is out of bounds.
            throw new IllegalArgumentException("Priority out of range: " + newPriority);
        }
        if((g = getThreadGroup()) != null) {
    
            // 如果线程优先级高于组max优先级,则设置为组的max优先级
            if (newPriority > g.getMaxPriority()) {
    
                newPriority = g.getMaxPriority();
            }
            synchronized(this) {
    
                this.priority = newPriority;
                if (isAlive()) {
    
                    nativeSetPriority(newPriority);
                }
            }
        }
    }

首先他先给定了优先级设置的范围:1-10

public final static int MIN_PRIORITY = 1;
public final static int MAX_PRIORITY = 10;

然后如果设置优先级大于组最大优先级了,则设置为组最大优先级。
最后,就是调用到nativeSetPriority,我们看一下主要实现:

static void Thread_nativeSetPriority(JNIEnv* env, jobject java_thread, jint new_priority) {
    
     if (thread != nullptr) {
    
          thread->SetNativePriority(new_priority);
      }
}

我们看一下实现,代码位于art/runtime/thread_android.cc:

void Thread::SetNativePriority(int newPriority) {
    
  if (newPriority < 1 || newPriority > 10) {
    
    LOG(WARNING) << "bad priority " << newPriority;
    newPriority = 5;
  }

  int newNice = kNiceValues[newPriority-1];
  pid_t tid = GetTid();

  if (newNice >= ANDROID_PRIORITY_BACKGROUND) {
    
    set_sched_policy(tid, SP_BACKGROUND);
  } else if (getpriority(PRIO_PROCESS, tid) >= ANDROID_PRIORITY_BACKGROUND) {
    
    set_sched_policy(tid, SP_FOREGROUND);
  }
  if (setpriority(PRIO_PROCESS, tid, newNice) != 0) {
    
    PLOG(INFO) << *this << " setPriority(PRIO_PROCESS, " << tid << ", " << newNice << ") failed";
  }
}

整个过程,就是先判断优先级设置是否不在区间内,不在的话,统一设置成5。
我们再看一下kNiceValues具体怎么对应到上层的10个优先级:

static const int kNiceValues[10] = {
    
  ANDROID_PRIORITY_LOWEST,                // 1 (MIN_PRIORITY)
  ANDROID_PRIORITY_BACKGROUND + 6,
  ANDROID_PRIORITY_BACKGROUND + 3,
  ANDROID_PRIORITY_BACKGROUND,
  ANDROID_PRIORITY_NORMAL,                // 5 (NORM_PRIORITY)
  ANDROID_PRIORITY_NORMAL - 2,
  ANDROID_PRIORITY_NORMAL - 4,
  ANDROID_PRIORITY_URGENT_DISPLAY + 3,
  ANDROID_PRIORITY_URGENT_DISPLAY + 2,
  ANDROID_PRIORITY_URGENT_DISPLAY         // 10 (MAX_PRIORITY)
};

而就算我们分了10级执行,最终android都会分成这三步设置:
1、如果nice值>ANDROID_PRIORITY_BACKGROUND,设置调度策略为SP_BACKGROUND,关于调度策略函数set_sched_policy我们后面还会提到。
2、如果根据CPU策略,也就是getpriority(PRIO_PROCESS, tid)获取的优先级是大于ANDROID_PRIORITY_BACKGROUND,则调度策略为SP_FOREGROUND。
3、最后setpriority(PRIO_PROCESS, tid, newNice)就是真正设置优先级的调用,PRIO_PROCESS表示设置进程的优先级。tid就表示对应的进程/进程组/用户id,newNice就表示进程的nice值,到这一步其实可以发现,线程的调度最后也是转换成了进程调度(进程调度的依据就是nice值,越小优先级越高),这个我们后面会详细解释。
从这个过程我们可以看到,设置thread分级的10个优先级,并不能有效的让线程出于更高的优先级,因为他会把线程的优先级转换成nice值,这个转换过程也就说明优先级不能完全对等nice值。那么线程优先级我们怎么对等进程的优先级设置等级,Android提供了android.os.Process的:

public static final native void setThreadPriority(int priority)
        throws IllegalArgumentException, SecurityException;

才能真正有效的设置线程的优先级,这边主要依赖的就是进程的优先级调度,我们看代码frameworks/base/core/jni/android_util_Process.cpp:

void android_os_Process_setThreadPriority(JNIEnv* env, jobject clazz,
                                              jint pid, jint pri)
{
    
    int rc = androidSetThreadPriority(pid, pri);
    if (rc != 0) {
    
        if (rc == INVALID_OPERATION) {
    
            signalExceptionForPriorityError(env, errno, pid);
        } else {
    
            signalExceptionForGroupError(env, errno, pid);
        }
    }
}

主要就是调用androidSetThreadPriority函数,代码位置/system/core/libutils/Threads.cpp:

int androidSetThreadPriority(pid_t tid, int pri)
{
    
    int rc = 0;
    int lasterr = 0;

    if (pri >= ANDROID_PRIORITY_BACKGROUND) {
    
        rc = set_sched_policy(tid, SP_BACKGROUND);
    } else if (getpriority(PRIO_PROCESS, tid) >= ANDROID_PRIORITY_BACKGROUND) {
    
        rc = set_sched_policy(tid, SP_FOREGROUND);
    }

    if (rc) {
    
        lasterr = errno;
    }

    if (setpriority(PRIO_PROCESS, tid, pri) < 0) {
    
        rc = INVALID_OPERATION;
    } else {
    
        errno = lasterr;
    }

    return rc;
}

这个过程跟Thread设置优先级的函数近似,只是没有了转换过程,故而也就讲线程的优先级完全对等到了进程优先级,这样才能真正生效,我们再看一下遗留的问题set_sched_policy如何设置调度策略的,代码在/system/core/libcutils/sched_policy.cpp:

int set_sched_policy(int tid, SchedPolicy policy)
{
    
    if (tid == 0) {
    
        tid = gettid();
    }`
    policy = _policy(policy);
    pthread_once(&the_once, __initialize); // 初始化policy的fd

     // 是否开启boost的调度方式
    if (schedboost_enabled()) {
    
        int boost_fd = -1;
        switch (policy) {
    
        case SP_BACKGROUND:
            boost_fd = bg_schedboost_fd;
            break;
        case SP_FOREGROUND:
            boost_fd = fg_schedboost_fd;
            break;
        default:
            boost_fd = -1;
            break;
        }

        // 将进程加入到对应的调度策略组
        if (boost_fd > 0 && add_tid_to_cgroup(tid, boost_fd) != 0) {
    
            if (errno != ESRCH && errno != ENOENT)
                return -errno;
        }

    }

    // 设置timerslack,这个表示进程可睡眠的最长时间间隔,比如你在用户态sleep了100ms,但是timerslack大于100ms,那么实际线程睡眠的时间会超过设置的sleep时长
    set_timerslack_ns(tid, policy == SP_BACKGROUND ? TIMER_SLACK_BG : TIMER_SLACK_FG);

    return 0;
}

首先看一下initialize初始化fd的操作

static void __initialize() {
    
    const char* filename;

    if (cpusets_enabled()) {
    
        if (!access("/dev/cpuset/tasks", W_OK)) {
    
            // 首先如果CPU支持调度策略,则打开cpu对应的fd
            filename = "/dev/cpuset/foreground/tasks";
            fg_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
            filename = "/dev/cpuset/background/tasks";
            bg_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
            filename = "/dev/cpuset/system-background/tasks";
            system_bg_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
            filename = "/dev/cpuset/top-app/tasks";
            ta_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
            filename = "/dev/cpuset/restricted/tasks";
            rs_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
            
            // 如果支持boost的调度策略,初始化boost调度侧率fd
            if (schedboost_enabled()) {
    
                filename = "/dev/stune/top-app/tasks";
                ta_schedboost_fd = open(filename, O_WRONLY | O_CLOEXEC);
                filename = "/dev/stune/foreground/tasks";
                fg_schedboost_fd = open(filename, O_WRONLY | O_CLOEXEC);
                filename = "/dev/stune/background/tasks";
                bg_schedboost_fd = open(filename, O_WRONLY | O_CLOEXEC);
                filename = "/dev/stune/rt/tasks";
                rt_schedboost_fd = open(filename, O_WRONLY | O_CLOEXEC);
            }
        }
    }

    char buf[64];
    snprintf(buf, sizeof(buf), "/proc/%d/timerslack_ns", getpid());
    __sys_supports_timerslack = !access(buf, W_OK);
}

然后我们看一下策略是如何应用的,主要调用函数:

static int add_tid_to_cgroup(int tid, int fd)
{
    
    if (fd < 0) {
    
        SLOGE("add_tid_to_cgroup failed; fd=%d\n", fd);
        errno = EINVAL;
        return -1;
    }

    // specialized itoa -- works for tid > 0
    char text[22];
    char *end = text + sizeof(text) - 1;
    char *ptr = end;
    *ptr = '\0';
    while (tid > 0) {
    
        *--ptr = '0' + (tid % 10);
        tid = tid / 10;
    }

    if (write(fd, ptr, end - ptr) < 0) {
    
        /*
         * If the thread is in the process of exiting,
         * don't flag an error
         */
        if (errno == ESRCH)
                return 0;
        SLOGW("add_tid_to_cgroup failed to write '%s' (%s); fd=%d\n",
              ptr, strerror(errno), fd);
        errno = EINVAL;
        return -1;
    }

    return 0;
}

这个过程实际上就是按照规则将进程id写入到对应策略的fd即可。这样cpu在调度的时候就会知道当前进程是怎样调度的。而且从这个分组概念中,我们也看到,每个调度策略有独立的组,这样不同的组使用不同样的调度方式。而且也可以看到,这种设置策略的方式,仅使用与支持boost调度的系统,不然调度策略都是默认的。还有一种是依赖cpu的调度方式,见Process.java的代码setThreadGroupAndCpuset以及相关调用,这里不再继续。
有了调度策略,我们看一下最终设置优先级的函数setpriority,这个实际上就是系统调用,这个就涉及到kernel代码的实现,我们这边就看一下setpriorit这个函数的功能:

int setpriority(int which, int who, int prio);

其中which的取值:PRIO_PROCESS, PRIO_PGRP, PRIO_USER分别表示设置进程
who取值则是相对于which定义的id,比如which是PRIO_PROCESS,就表示进程id,which是PRIO_PGRP,它就表进程组id,which是PRIO_USER,它就表示用户id
prio也就是nice值,nice值的取值是-19-20,nice值月底,优先级越高。
返回值int,取值有:
EINVAL which值错误,不是PRIO_PROCESS、PRIO_PGRP和PRIO_USER其中之一
ESRCH 根据who没有找到相关对象
EACCES 无法降低优先级
EPERM 权限不够

4、总结

经过我们的分析,我们知道线程和进程在内核中都是以进程表现。只是线程之间会公用pid,公用内存空间,但是各自栈独立。这也就促使我们线程优先级调节需要借助进程切换优先级的接口setpriority。
线程调用start的运行线程的时候,本质上还是调用libc的pthread_create接口,也就是说虚拟机中的线程与linux的线程是一一对应的,在底层创建线程成功之后,才会回调java层调用run的实现。这也就是为什么我们在运行线程的时候,一定要调用start的原因。
首个线程创建时即为主线程main,并且其线程组也叫主线程组main。如果创建线程的时候,未设置线程组,则线程组使用父线程的线程组。相应的如果创建线程组时,此线程组的父线程组就是当前线程的线程组。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/luoluofeixia/article/details/106855989

智能推荐

网络爬虫_Cerise。�的博客-程序员秘密_网络爬虫资源库节点

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入欢迎使用Ma...

91. Decode Ways, leetcode_yuccess的博客-程序员秘密

题目:A message containing letters from A-Z is being encoded to numbers using the following mapping:'A' -> 1'B' -> 2...'Z' -> 26Given an encoded message containing digits, determine the

一张图概括编程语言发展史_ithewei的博客-程序员秘密

一张图概括编程语言发展史一张图概括编程语言发展史Intro编年史Intro编程语言是一组用来定义计算机程序的语法规则。它是一种被标准化的交流语言,用来向计算机发出指令。一种计算机语言让程序员能够准确地定义计算机所需要使用的数据,并精确地定义在不同情况下所应当采取的行动。尽管人们多次试图创造一种通用的程序设计语言,却没有一次尝试是成功的。之所以有那么多种不同的编程...

Unity3D面试题汇总_taotaoahui的博客-程序员秘密_unity3d毕业设计题目什么最简单

http://blog.csdn.net/yaokang522/article/details/464148631.请描述游戏动画有哪几种,以及其原理。2.alpha blend 工作原理3.写光照计算中的diffuse的计算公式4.lod是什么,优缺点是什么5.两种阴影判断的方法工作原理6.MipMap是什么?作用?7.用u3d实现2d

Object-C 介绍_子龙哦的博客-程序员秘密_object-c

介绍Object-C 是一个为苹果IOS 和OS X系统编写程序的语言。它能够编译和构建命令行功能、GUI、领域框架的通用语言。他也提供了很多的功能帮助维护复杂的应用。 像C++一样,Object-C也是在C语言的基础上添加了面向对象的特性,但是两种语言完整这个目标,采用了明显不同的基础哲学。Object-C明显更加的偏向动态语言,推迟它的决定到运行时,而不是编译时。这个表现在IOS 和 OS X

STM32入门开发: LWIP网络协议栈移植(网卡采用DM9000)_DS小龙哥的博客-程序员秘密_lwip移植

一、环境介绍MCU: STM32F103ZET6代码开发工具: Keil5TCP/IP协议栈: LWIP网卡: DM9000本篇文章主要讲解如何在STM32F103工程里添加移植LWIP协议,最终完成TCP服务器、TCP客户端的通信测试。 网卡采用的是DM9000,工程代码中,采用STM32的FSMC接口来驱动DM900网卡,DM9000是并口网卡,引脚多,但是速度快,也可以采用其他网卡,SPI协议的、UART协议的等。 比如:W5500。 因为主要是讲LWIP协议栈的移植,所以网卡相关

随便推点

Oracle问题汇总_Web图形学工作站的博客-程序员秘密

1、oracle建表 (先建表空间-》再建用户同时和表空间联系到一起-》授权DBA)在创建好数据实例(数据库)好后的基础上,后续做的事情如下:—创建表空间create tablespace LIS2011DATAloggingdatafile ‘d:\oracle\product\10.2.0\ordata\lis2011\LIS2011DATA01.dbf’size 32mauto...

vue如何拖动改变组件大小_Vue2组件,用于可拖动和可调整大小的元素_cuk5239的博客-程序员秘密

vue如何拖动改变组件大小 VueDraggableResizable (VueDraggableResizable)Vue2 Component for draggable and resizable elements. Vue2可拖动和可调整大小元素的组件。 特征 (Features)No dependenciesUse draggable, resizable or bothD...

【C语言】编程实现输出矩阵上/下三角的数值。要求:输入一个正整数n和n阶矩阵的数值,打印输出矩阵、下三角和上三角的数值。_团子团子大团子的博客-程序员秘密_编程实现输出矩阵上下三角的数值

【问题描述】编程实现输出矩阵上/下三角的数值。要求:输入一个正整数n和n阶矩阵的数值,打印输出矩阵、下三角和上三角的数值。【输入输出样例】【样例说明】输入提示符中冒号为英文符号,后面无空格。输出矩阵时整数按照%4d格式输出。...

linux 信号量sem_t和pthread_cond_t 的区别_blog.pytool.com的博客-程序员秘密_sem_t和cond_t区别

尽量使用 sem 代替 pthread_cond因为 pthread_cond 会有丢失信号的问题,sem 是原子操作,所以不会丢失信号Mac 下对 sem_init()/sem_destory() 不支持:注意:MacOS 不支持sem_init()和sem_destroy();这个例子若是想在 mac 下编译通过,需要自行修改替换相关的函数。sem_init(&amp;sem, 0, 1)改成sem_open("sem", O_CREAT|O_EXCL, S_...

s+=1 和 s=s+1看了这篇,你会明白好多自己不知道的(对小白)_zqhwboy的博客-程序员秘密_s+=1

s+=1和s=s+1区别下面的代码会报错,知道为什么么? short s = 1; s = s + 1;因为:1是int型,s+1会自动将s转化为int型进行运算,结果就是int型,果将int直接赋值给short会报错,高到第的转化需要强转.正确的写成下面这样 //注意加两个括号 short s = 1; s = (short) (s + 1);如果改成这样会对么? short s = 1; s = s + (short)1;

mybatis常用jdbcType数据类型与mysql的类型对照_晓未苏的博客-程序员秘密

&lt;resultMap type="java.util.Map" id="resultjcm"&gt;&lt;result property="FLD_NUMBER" column="FLD_NUMBER"javaType="double" jdbcType="NUMERIC"/&gt;&lt;result property="FLD_VARCHAR" column="...

推荐文章

热门文章

相关标签