一.概念
线程:一个进程内部的控制序列。或者说在一个程序里的一个执行路线
首先明确一个概念,在Linux下是没有进程的控制块的,使用进程模拟的线程。一个进程中至少有一个线程。所以进程跟线程的数量是一对(一)多的。
线程跟进程的区别
1.进程是资源分配的基本单位,线程是CPU调度的基本单位。
2.创建一个线程要比创建一个进程更加轻量化,所以我们一般也把线程叫做轻量级进程。
3.进程之间是相互独立的,一个进程的异常不会导致另一个进程的异常;而线程由于共享地址空间,访问共同资源,如果一个线程异常,可能会导致所有的线程都异常。
4.进程之间数据各自私有,互不干扰,而线程大部分资源都是共享的,但也有部分资源各自私有,比如线程ID,线程栈,各自的寄存器等等。
5.进程间通信是比较麻烦的,而线程由于共享数据,通信是很容易的。
以下用到的所有的函数的头文件都是#include <pthread.h>
,用到的函数在gcc(g++)编译的时候都要加上 -lpthread
创建线程
要知道,创建线程是比创建进程更轻量化的,创建进程要创建PCB,虚拟地址空间,页表,还要在物理内存上存储存储该进程的数据等等,而在Linux下使用进程模拟的线程,则创建线程只需要创建一个PCB即可,该PCB也指向虚拟地址空间。即线程所用数据就是虚拟地址空间上的数据。我们把创建进程时创建的PCB称为主线程。
线程创建函数:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
thread:输出型参数,返回线程ID
attr:设置线程的属性,一般为NULL即可
start_routine:一个函数指针,即线程所要执行的函数,返回值void*,参数void*
arg:传给start_routine的参数。
返回值:创建成功返回0,创建失败返回错误码。
线程等待
为什么要线程等待?
因为已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。如果不进行线程等待,就会发生类似于僵尸进程的问题。所以必须线程等待。
线程等待函数:
int pthread_join(pthread_t thread, void **retval);
thread:线程,表明要等哪个线程
retval:是pthread_create中start_routine的返回值,一般在主线程内定义void*变量,将该变量地址传入,获取start_routine的返回信息。不关心可设为NULL.
返回值:成功返回0,失败返回错误码。
测试样例:
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;
void* run1(void*arg)
{const char* msg = (const char*)arg;while(true){cout<<msg<<endl;sleep(1);}
}
void* run2(void*arg)
{const char* msg = (const char*)arg;while(true){cout<<msg<<endl;sleep(1);}
}
int main()
{pthread_t t1,t2;const char* s1 = "I am a thread1";const char* s2 = "I am a thread2";pthread_create(&t1,nullptr,run1,(void*)s1);pthread_create(&t2,nullptr,run2,(void*)s2);pthread_join(t1,nullptr);pthread_join(t2,nullptr);return 0;
}
运行结果
线程ID和进程ID
进程ID是每一个进程独有的ID,可以通过getpid函数获得,也可以通过 ps -ajx查看属于进程ID,而线程ID是每一个线程独有的ID,当使用pthread_create函数时,通过第一个参数将线程ID传出,也可以使用函数pthread_self
pthread_t pthread_self(void)
无参,返回调用这个函数的线程ID
也可以使用ps -aL查看线程ID
我们创建一个线程并通过pthread_self和ps -aL分别查看它的ID
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;
void* run1(void*arg)
{char* msg = (char*)arg;cout<<arg<<pthread_self()<<endl;sleep(1);
}
int main()
{pthread_t t;pthread_create(&t,nullptr,run1,(void*)"pthread id:");pthread_join(t,nullptr);return 0;
}
pthread_self查看的线程ID
ps -aL查看的线程ID(LWP)
发现这两个得到的ID是不一样的。pthread_self得到的是非常大的一个数字,ps -aL得到的LWP是比较正常的,这是为什么呢?
答案:LWP得到的线程ID是真正的线程ID。pthread_self得到的这个数实际上是一个地址,在虚拟地址空间上的一个地址,通过这个地址,可以找到关于这个线程的基本信息,包括线程ID,线程栈,寄存器等属性。
注:
在ps -aL得到的线程ID,有一个线程ID和进程ID相同,这个线程就是主线程,主线程的栈在虚拟地址空间的栈上,而其他线程的栈在是在共享区(堆栈之间),因为pthread系列函数都是pthread库提供给我们的。而pthread库是在共享区的。所以除了主线程之外的其他线程的栈都在共享区。