线程共享进程数据,但也拥有自己的一部分数据:
因为在在同一个地址空间,所以所谓的代码段,数据段都是共享的。
除此之外,各线程还共以下资源和环境:
进程和线程的关系,例如:
之前,我们都是以单线程进程学习为主,以后我们也将尝试解除单进程多线程学习。
创建线程的函数为pthread_create,原型如下:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
参数说明:
返回值说明:
注意:
Linux不能真正意义上的帮我们提供线程的接口,但是Linux有原生线程库,使用此函数必须在编译时带上 -pthread 选项。
以下例子中,我们让主线程创建一个新线程,预计主线程与新线程分别去执行对应的函数代码。
void* Routine(void* arg)
{
char* msg = (char*)arg;
while (1){
cout << " i am a thread 1 " << endl;
sleep(1);
}
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, Routine, (void*)"thread 1");
while (1){
cout << " I am a main thread " << endl;
sleep(2);
}
return 0;
}
结果如下:
当然我们也可以使用 ps -ajx 命令来查看当前进程信息,但是,使用该命令只查到了mythread进程相关信息,没有显示其他的线程。
所以,我们可以使用 ps -aL 命令,来显示当前进程中的线程信息。其中LWP( Light Weight Process )就是代表该线程的ID,可以看到,这两个线程的PID是一样的,就代表它们同属于一个进程。
我们以前学习进程的时候认为OS调度的时候以PID为准,实际上OS调度的时候采用的是PWD,只不过主线程的PWD和PID是一样的,所以单线程进程调度时采用PID和PWD实际上是一样的。
我们可以调用pthread_self函数获取线程PWD。
函数原型如下:
pthread_t pthread_self(void);
以下代码,我们通过pthread_self函数分别打印主线程和新线程的PID和PWD。
void *threadRun( void *args )
{
const string name = ( char * )args;
int count = 0;
while( count < 5 )
{
cout << name << " pid: " << getpid() << " PWd "<< pthread_self()<< endl;
sleep(1);
++count;
}
return nullptr;
}
int main()
{
pthread_t tid[5];
char name[64];
for ( long long i = 0; i < 5; ++i )
{
snprintf( name, sizeof name, "%s - %d", "thread", i );
pthread_create( tid + i,NULL,threadRun, (void *)name );
sleep(1);
}
cout << " i am a main thread " << " getpid: " << getpid() << " PWD " << pthread_self() << endl;
return 0;
}
首先,我们应该注意的是,一个线程被创建出来,这个线程就如同进程一般,也是需要被等待的。如果主线程不对新线程进程等待,那么新线程资源是不会被回收的。此时,便有了pthread_join函数专门对新线程处理。
函数原型如下:
int pthread_join(pthread_t thread, void **retval);
参数说明:
返回值说明:
线程等待成功返回0,失败返回错误码。
如果thread线程通过return返回,retal所指向的单元里存放的是thread线程函数的返回值。
如果thread线程被别的线程调用pthread_ cancel异常终掉,retal所指向的单元里存放的是常数PTHREAD_ CANCELED,该常数值为-1。
如果thread线程是自己调用pthread_exit终止的,retal所指向的单元存放的是传给pthread_exiit的参数。
如果对thread线程的终止状态不感兴趣,可以传NULL给retal参数。
例如,以下代码主线程创建一个新线程后,阻塞等待新线程打印10次后退出,主线程也随之退出。
void* threadRoutine( void* args )
{
int i = 0;
while( true )
{
cout << "新线程: " << ( char* )args << " running... " << endl;
sleep(1);
if( i++ == 10 ) break;
}
cout << "new thread quit... " << endl;
return nullptr;
}
int main()
{
pthread_t tid;
pthread_create( &tid,nullptr,threadRoutine,(void*)"thread 1 ");
pthread_join( tid,nullptr );
cout<< " main thread wait done ... main quit " << endl;
}
pthread_join第二个参数
当新线程退出后,我们可以对新线程返回值设置特定值,但是需要将该值以地址的形式返回。新线程退出时,由主线程中的ret指针保存,但是如果需要改变一级指针保存的数据需要传入二级指针(ret的地址)才能获取到ret进而改变。
void* threadRoutine( void* args )
{
int i = 0;
while( true )
{
cout << "新线程: " << ( char* )args << " running... " << endl;
sleep(1);
if( i++ == 10 ) break;
}
cout << "new thread quit... " << endl;
return (void*)10;
}
int main()
{
pthread_t tid;
pthread_create( &tid,nullptr,threadRoutine,(void*)"thread 1 ");
void* ret = nullptr;
pthread_join( tid,&ret );
cout<< " main thread wait done ... main quit " << " exitcode: " << (long long )ret<< endl;
}
结果如下:
我们知道,每个线程的栈是私有的,但是我们也可以通过 pthread_join第二个参数来获取,这更加体现了主新线程之间的数据传输。
例如: 我们在threadRoutine例程中创建了一个数组,并通过返回值返回由ret指针接受。
void* threadRoutine( void* args )
{
int i = 0;
int* data = new int[11];
while( true )
{
cout << "新线程: " << ( char* )args << " running... " << endl;
sleep(1);
data[i] = i;
if( i++ == 10 ) break;
}
cout << "new thread quit... " << endl;
return (void*)data;
}
int main()
{
pthread_t tid;
pthread_create( &tid,nullptr,threadRoutine,(void*)"thread 1 ");
int* ret = nullptr;
pthread_join( tid,(void**)&ret );
//cout<< " main thread wait done ... main quit " << " exitcode: " << endl;
for( int i = 0; i < 10; i++ )
{
cout << ret[i] << endl;
}
return 0;
}
线程出现异常吗,整个进程也出现异常。
在以上的代码中,我们在例程中写出除0错误,当该线程崩溃时,整个进程也将随即崩溃,此时再获取线程的退出码也没有意义。
void* threadRoutine( void* args )
{
int i = 0;
int* data = new int[11];
while( true )
{
cout << "新线程: " << ( char* )args << " running... " << endl;
sleep(1);
data[i] = i;
if( i++ == 10 ) break;
int a = 100;
a /= 0;
}
cout << "new thread quit... " << endl;
return (void*)data;
}
int main()
{
pthread_t tid;
pthread_create( &tid,nullptr,threadRoutine,(void*)"thread 1 ");
int* ret = nullptr;
pthread_join( tid,(void**)&ret );
//cout<< " main thread wait done ... main quit " << " exitcode: " << endl;
for( int i = 0; i < 10; i++ )
{
cout << ret[i] << endl;
}
return 0;
}
如果需要只终止某个线程而不是终止整个进程,可以有三种方法:
终止线程pthread_exit
pthread_exit函数的功能就是终止线程,pthread_exit函数的函数原型如下:
void pthread_exit(void *retval);
参数说明:
retval:线程退出时的退出码信息。
例如: 我们使用Pthread_exit函数终止进程,并将退出码设为10。
void* threadRoutine( void* args )
{
int i = 0;
int* data = new int[11];
while( true )
{
cout << "新线程: " << ( char* )args << " running... " << endl;
sleep(1);
data[i] = i;
if( i++ == 10 ) break;
}
cout << "new thread quit... " << endl;
pthread_exit((void*)10);
}
int main()
{
pthread_t tid;
pthread_create( &tid,nullptr,threadRoutine,(void*)"thread 1 ");
int* ret = nullptr;
pthread_join( tid,(void**)&ret );
cout<< " main thread wait done ... main quit " << " exitcode: " << ( long long ) ret << endl;
return 0;
}
注意:
exit函数的作用是终止进程,任何一个线程调用exit函数也代表的是整个进程终止。
终止进程 pthread_cancel
我们可以通过pthread_cancel函数取消某一个线程,该函数原型如下:
int pthread_cancel(pthread_t thread);
参数说明:
thread:被取消线程的ID。
返回值说明:
线程取消成功返回0,失败返回错误码。
例如: 我们让新线程执行一段时间,随后主线程调用pthread_cancel函数取消该新线程,我们一般都是由主线程取消新线程,( 这是pthread_cancel 的常规用法 )
void* threadRoutine( void* args )
{
int i = 0;
int* data = new int[11];
while( true )
{
cout << "新线程: " << ( char* )args << " running... " << endl;
sleep(1);
data[i] = i;
if( i++ == 10 ) break;
}
cout << "new thread quit... " << endl;
pthread_exit((void*)10);
}
int main()
{
pthread_t tid;
pthread_create( &tid,nullptr,threadRoutine,(void*)"thread 1 ");
int count = 0;
while( true )
{
cout << "main线程: " << "running..." << endl;
sleep(1);
count++;
if( count >= 5 ) break;
}
pthread_cancel(tid);
int* ret = nullptr;
pthread_join( tid,(void**)&ret );
cout<< " main thread wait done ... main quit " << " exitcode: " << ( long long ) ret << endl;
return 0;
}
结果如下:
我们可以看出,此时的新线程返回值不再是我们原先设置的10,因为该新线程是由pthread_cancel函数取消终止的,OS默认设置其返回值为-1.
pthread_detach函数原型如下:
int pthread_detach(pthread_t thread);
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离,但是常规情况下,我们一般让新线程自己分离。
void* threadRoutine( void* args )
{
pthread_detach(pthread_self());
while( true )
{
cout << "新线程: " << ( char* )args << endl;
sleep(1);
}
cout << "new thread quit... " << endl;
pthread_exit((void*)10);
}
int main()
{
pthread_t tid;
pthread_create( &tid,nullptr,threadRoutine,(void*)"thread 1 ");
int count = 0;
while( true )
{
cout << " main 线程 " << endl;
sleep(1);
count++;
if( count >= 5 ) break;
}
cout<< " main thread wait done ... main quit " << endl;
return 0;
}
注意:
joinable和分离是冲突的,一个线程不能既是joinable又是分离的,并且在常规线程分离的场景中,主线程一般用来创建新线程处理任务和回收资源,一般都是最后退出的。如果主线程先退出,就意味着进程退出,那么新线程也立刻会随即退出。
线程ID本质上是一个地址
当进程运行时,pthread共享库即加载到物理内存中,再根据页表,映射到进程地址空间中的共享区。
主线程和新线程都含有各自的独立栈结构来保存每一个线程都是独立的,主线程用的是内核级的栈结构,每一个新线程都含有共享区中独有的pthread库中的栈结构。为了对这些属性数据进行管理,OS采用了“先描述,再组织”的方式,该动态库中包含了一个个struct pthread结构体,其中包含了线程栈,上下文等数据,而线程ID(tid)便是动态库中每一个struct pthread结构体的首地址,进而CPU通过tid来找到对应的线程。
打印线程ID
我们现在可以对线程ID进行打印。
void* threadRoutine( void* args )
{
int i = 0;
int* data = new int[11];
while( true )
{
cout << "新线程: " << ( char* )args << " running... " << endl;
sleep(1);
data[i] = i;
if( i++ == 10 ) break;
}
cout << "new thread quit... " << endl;
pthread_exit((void*)10);
}
int main()
{
pthread_t tid;
pthread_create( &tid,nullptr,threadRoutine,(void*)"thread 1 ");
printf( " %lu , %p \n ",tid,tid );
int count = 0;
while( true )
{
cout << "main线程: " << "running..." << endl;
sleep(1);
count++;
if( count >= 5 ) break;
}
pthread_cancel(tid);
int* ret = nullptr;
pthread_join( tid,(void**)&ret );
cout<< " main thread wait done ... main quit " << " exitcode: " << ( long long ) ret << endl;
return 0;
}
线程的局部存储
我们知道,全局变量,已初始化数据,未初始化数据等都是线程间共享的。但是,我们可以在全局变量前添加__pthread 代表每一个线程都含有该独有的全局变量保存在每一个线程局部存储变量中。
例如: 我们分别通过主线程和新新线程打印全局变量g_val的值和地址。
__thread int g_val = 0;
void* threadRoutine( void* args )
{
int i = 0;
int* data = new int[11];
while( true )
{
cout << "新线程: " << ( char* )args << " g_val: " << g_val << " &g_val " << &g_val << endl;
++g_val;
sleep(1);
}
cout << "new thread quit... " << endl;
pthread_exit((void*)10);
}
int main()
{
pthread_t tid;
pthread_create( &tid,nullptr,threadRoutine,(void*)"thread 1 ");
int count = 0;
while( true )
{
cout << "main线程: " << " g_val " << g_val << " &g_val " << &g_val << endl;
sleep(1);
count++;
if( count >= 5 ) break;
}
pthread_cancel(tid);
int* ret = nullptr;
pthread_join( tid,(void**)&ret );
cout<< " main thread wait done ... main quit " << " exitcode: " << ( long long ) ret << endl;
return 0;
}
结果如下:
我们可以看到,主线程g_val值没有变化,而新线程g_val每一次打印都增加了1,并且主新线程中的g_val的地址是不同的。