对页表的再次理解(以32位为例)
线程在进程内部执行,是OS调度的基本单位。
如何理解线程?
-------------------
上图中,每一个task_struct就是一个线程,红色方框内是属于一个进程。
创建线程,不用构建新的进程地址空间,页表,所以创建线程比创建进程更轻量化。
创建线程,只需要利用主线程(原进程)的地址空间,页表等资源。
在CPU看来,并不关心执行流是进程还是线程,只认task_struct,此时,可以说Linux没有真正意义上的线程结构,是用进程task_struct模拟线程的,Linux下的进程,统称为:轻量级进程!!!
-----------------------------------------------------------------------------------------------------------------------------
如何理解进程?
--------------------
用户视角:内核数据结构+对应的代码和数据!
内核视角:承担分配系统资源的实体!
----------------------------------------------------------------------------------------------------------------------------
线程的优点
线程的缺点
线程异常
因为创建的新线程,与主线程共用地址空间,页表等,所以新线程出现异常就会引起整个线程组异常。
线程用途
进程VS线程
进程是资源分配的基本单位;
线程是调度的基本单位;
线程共享进程数据,但也拥有自己的一部分数据:
关于主线程和新线程对栈的使用
主线程和子线程用的栈不在同一个区域,主线程用的栈就在地址空间的栈区,但是新线程用的栈区是在pthread库中对应的区域。
那么,怎么区分各个新线程的栈?
void *threadRun(void *args)
{
const string name = (char *)args;
while (true)
{
cout<<pthread_self()<<endl;
sleep(1);
}
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, threadRun, (void *)"new thread");
while (true)
{
cout << "main thread, pid: " << getpid() << endl;
sleep(1);
}
}
输出结果:
我们发现,新线程的ID值非常大,我们在哪里见到过这么大的值那?--------虚拟地址!!!
其实不然,新线程的ID值,就是用来标识该线程在pthread库中对应的存储信息的起始位置,可以结合前边的图看到。
进程的多个线程共享 同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
主线程与新线程共用全局变量
void *threadRoutine(void *args)
{
pthread_detach(pthread_self());
while(true)
{
cout << (char*)args << " : " << g_val << " &: " << &g_val << endl;
g_val++;
sleep(1);
}
pthread_exit((void*)11);
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");
while(true)
{
cout << "main thread" << " : " << g_val << " &: " << &g_val << endl;
sleep(1);
}
return 0;
}
部分打印结果: (我们发现,新线程修改全局变量,主线程的也会改变,而且地址是一样的!)
如果用 __thread 来修饰全局变量的话,会发现新线程修改全局变量,主线程看到的值并不会发生改变,而且二者对应的地址不同。
进程ID和线程ID
认识进程ID和线程ID
void *threadRun(void *args)
{
const string name = (char *)args;
while (true)
{}
}
int main()
{
pthread_t tid[5];
for (int i = 0; i < 5; i++)
{
pthread_create(tid + i, nullptr, threadRun, (void *)"");
}
while (true)
{}
}
ps 命令中的 -L 选项,会显示如下信息:
其中,PID值和LWP值相同的是主线程,其余的都是新线程。
线程库 NPTL 提供了 pthread_ self 函数,可以获得线程自身的 ID :
线程控制
POSIX线程库
创建线程
线程终止
如果需要只终止某个线程而不终止整个进程 , 可以有三种方法 :
pthread_exit函数
pthread_cancel函数
线程等待
调用该函数的线程将挂起等待 , 直到 id 为 thread 的线程终止。 thread 线程以不同的方法终止 , 通过 pthread_join 得到的终止状态是不同的,总结如下:
为什么需要线程等待?
分离线程
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离 :
关于新线程退出结果
void *threadRoutine(void *args)
{
while(true)
{
pthread_testcancel();
}
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");
int count = 0;
while (true)
{
count++;
if (count >= 5)
break;
}
int n= pthread_cancel(tid);
cout <<n<<endl;
cout << "pthread cancel: " << tid << endl;
int *ret = nullptr;
pthread_join(tid, (void **)&ret); // 默认会阻塞等待新线程退出
cout << "main thread wait done ... main quit ...: new thead quit : " << (long long)ret << "\n";
return 0;
}
如果调用pthread_cancel来终止线程,则线程的退出码是 -1。
当然,如果新线程指定返回退出结果,可通过(void*)进行强转,然后主线程再(long long)进行强转,就可获得。