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(env)->GetSelf();

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

ScopedObjectAccess soa(env);

ArtField* f = jni::DecodeArtField(WellKnownClasses::java_lang_Thread_name);

ObjPtr<:string> java_name =

f->GetObject(soa.Decode<: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 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(child_thread));

std::string error_msg;

std::unique_ptr 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(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<: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<:object> receiver = self->tlsPtr_.opeer;

jmethodID mid = WellKnownClasses::java_lang_Thread_run;

ScopedLocalRef ref(soa.Env(), soa.AddLocalReference(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。如果创建线程的时候,未设置线程组,则线程组使用父线程的线程组。相应的如果创建线程组时,此线程组的父线程组就是当前线程的线程组。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值