本节重点:
1.线程的基本概念,线程与进程的区别与联系。
2.线程id与进程id。
3.线程控制(线程创建、线程等待、线程终止)。
4.线程分离。
————————————————————————————————————————————————
1、线程的基本概念,线程与进程的区别与联系。
线程的概念:
<1>.线程是调度的基本单位、线程是一个进程内部的控制序列。
<2>.线程的执行力度比进程更细致、在进程的地址空间内运行。
<3>.一个进程至少有一个执行线程。线程与进程的区别与联系:
<1>.进程是承担分配系统资源的基本单位。是资源竞争的基本单位。
<2>.线程是调度的基本单位,是程序执行的最小单位。
<3>.线程共享进程数据。共享的数据如下:
a.由于同一进程的多个线程共享同一地址空间,所以代码段,数据段是共享的,如果定义一个函数(存储在代码段),各线程都可以进行调用,如果定义个全局变量(存储在数据段),在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
b.文件描述符表
c.每种信号的处理方式(SIG_IGN,SIG_DFL,用户自定义)
d.当前工作目录
e.用户id和组id
<4>.但是有自己的一部分数据是独享的:
a.线程ID
b.一组寄存器
c.栈:线程运行时会形成临时变量,但要保证线程间不被干扰。
d.信号屏蔽字
e.硬件上下文:包括各种寄存器的值,程序计数器和栈指针。
f.调度优先级
g.error线程与进程的联系:
分为单线程进程、多个单线程进程。
单进程多线程、多个多线程进程。
2.线程id与进程id。
在Linux中,线程又被称为轻量级进程(LWP),每一个用户态的线程,在内核中都对应一个调度实体,也有有自己的一张进程描述符(task_struct)结构体。
多线程的进程,又称为线程组,线程组内的每一个线程在内核中都存在一个进程描述符,与之对应。原因是Linux下的线程是由进程模拟来的。
进程描述符结构体中的pid,表面上看对应的是进程id,其实它对应的是线程id,进程描述符中的tgid,该值对应的是用户层面的进程id.
- 线程id:是pid_t类型的变量,是唯一用来标识线程的一个整型变量,即为LWP
LWP:线程id,是gettid()系统调用的返回值。
-当然还有一个函数也是返回线程的id:pthread_t pthread_self(void);
对于目前实现的POSIX下的线程来说,pthread_t类型的线程id,本质是一个进程地址空间上的一个地址。
好像有点绕。下面我就这两个函数进行说明两者的区别:
Linux中,每个进程有一个pid,类型pid_t,由getpid()取得。Linux下的POSIX线程也有一个id,类型 pthread_t,由pthread_self()取得,该id由线程库维护,其id空间是各个进程独立的(即不同进程中的线程可能有相同的id)。Linux中的POSIX线程库实现的线程其实也是一个进程(LWP),只是该进程与主进程(启动线程的进程)共享一些资源而已,比如代码段,数据段等。原因是Linux下的线程是由进程模拟的。
- linux多线程环境下gettid() pthread_self() 两个函数都获得线程ID,可它们的返回值不一样。
- linux使用进程模拟线程,gettid 函数返回实际的进程ID(内核中的线程的ID).
- pthread_self 函数返回 pthread_create创建线程时的ID(POSIX thread ID).pthread_t类型的线程id,本质是一个进程地址空间上的一个地址。
3.线程控制(线程创建、线程等待、线程终止)。
(1).创建线程: pthread_create函数
函数原型:
int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine)(void*),void*arg);
`函数功能:创建一个新的线程。
- 参数解释:
thread:返回线程id.
attr:设置线程属性,attr为NULL表示使用,默认属性。
start_routine:是一个函数地址,线程启动后要执行的函数。
arg:传给线程启动的参数。 - 返回值:成功返回0,失败返回错误码。
(2).线程终止:
- 线程终止的三种方式:
- 1.从线程函数中return,但是该方式对主线程不适用,从main函数中return相当于调用exit,使得进程推出。
- 2.线程可以调用pthread_exit函数终止自己。
- -3. 一个线程可以调用pthread_cancel函数终止同一进程中的另一个线程。
-函数介绍
- phread_exit函数:<1>.函数原型:
void pthread_exit(void*value_ptr);
<2>.函数功能:线程终止。
<3>.参数解释:value_ptr不要指向一个局部变量。
<4>.返回值:无返回值。
pthread_exit或者return 返回的指针所指向的内存单元必须是全局的或者是用malloc进行分配的,不能在线程函数栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
-pthread_cancel函数:<1>.函数原型:
int pthread_cancel(pthread_t thread);
<2>.函数功能:取消一个执行中的线程。他杀,杀掉另一个线程。
cancel后,线程不会立刻退出,而是要等到取消点才退出。
取消点:简单记成凡是系统调用的地方都是取消点。
<3>.参数解释:thread:线程id.
<4>.返回值:成功返回0,失败返回错误码。
(3).线程等待:
-等待的原因:已经退出的线程,其空间没有被释放,仍然在进程的地址空间内,所以要回收资源。
创建新的线程不会复用刚才推出线程的地址空间。
只有阻塞式等待,住线程等待线程结束。
函数原型:
int pthread_join(phtread_t thread,void**value_ptr);
参数解释:thread:线程ID,
- value_ptr:它指向一个指针,后者指向线程的返回值。
- 如果线程通过return返回,value_ptr所指向的单元里存放的是thread线程函数的返回值。
- 如果线程是被别的线程cancel掉,value_ptr所指向的单元里存放的是常数PTHREAD_CANCELED。
- 如果线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
- 如果对线程的终止状态不感兴趣,可以传NULL给value_ptr参数。返回值:成功返回0,失败返回错误码。
下面来看代码实例:(pthread1.c)
1.从线程函数中return。
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<pthread.h>
4
5 void *thread_run(void *arg)
6 {
7 printf("thread is running,pid:%d,thread:%d\n",getpid(),pthread_self());
8 sleep(3);
9 return (void*)3;
10 }
11 int main()
12 {
13 pthread_t t;
14 pthread_create(&t,NULL,thread_run,NULL);
15 printf("main thread is running!pid:%d,thread:%d\n",getpid(),pthread_self());
17 void *code;
18 int ret=pthread_join(t,&code);//线程等待
19 if(ret==0){
20 printf("wait new thread sucess,%d\n",(int)code);
21 }
22 }
~
结果显示:
2.线程可以调用pthread_exit函数终止自己。
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<pthread.h>
4
5 void *thread_run(void *arg)
6 {
7 printf("thread is running,pid:%d,thread:%d\n",getpid(),pthread_self());
8 sleep(3);
10 pthread_exit((void*)0);
11 }
12 int main()
13 {
14 pthread_t t;
15 pthread_create(&t,NULL,thread_run,NULL);
16 printf("main thread is running!pid:%d,thread:%d\n",getpid(),pthread_self());
17 //pthread_cancel(t);
18 void *code;
19 int ret=pthread_join(t,&code);
20 if(ret==0){
21 printf("wait new thread sucess,%d\n",(int)code);
22 }
23 }
~
结果显示:
3.主线程调用pthread_cancel函数终止另一个线程。
#include<stdio.h>
2 #include<unistd.h>
3 #include<pthread.h>
4
5 void *thread_run(void *arg)
6 {
7 printf("thread is running,pid:%d,thread:%d\n",getpid(),pthread_self());
8 sleep(3);
9 }
10 int main()
11 {
12 pthread_t t;
13 pthread_create(&t,NULL,thread_run,NULL);
14 printf("main thread is running!pid:%d,thread:%d\n",getpid(),pthread_self());
15 pthread_cancel(t);
16 void *code;
17 int ret=pthread_join(t,&code);
18 if(ret==0){
19 printf("wait new thread sucess,%d\n",(int)code);
20 }
21 }
结果显示:
4.线程分离。
线程的分离状态决定一个线程是以一个什么样的方式来终止自己,线程的默认属性一般是可结合的。
- 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_jion操作,否则无法释放资源,从而导致内存泄漏。
- 如果不关心线程的返回值,利用分离函数,一旦线程被设置为分离状态,代表该线程结束后不需要主动进行线程等待回收,其退出时,会资源自动释放。。。
函数原型:
int pthread_detach(pthread_t thread);
参数解释:thread:线程id.
线程组内其它线程对目标线程进行分离,也可以是线程自己分离。
只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的资源。
而分离线程没有被其他线程进行等待,自己运行结束了,线程也就终止了,其会立刻释放自己占用的资源。
下面用代码实现(pthread2.c)
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<pthread.h>
4
5 void *thread_run(void*arg)
6 {
7 char*a=(char*)arg;
8 pthread_detach(pthread_self());//自己已经将自己分离了
9 printf("%s\n",a);
10 return NULL;
11 }
12 int main()
13 {
14 pthread_t tid;
15 pthread_create(&tid,NULL,thread_run,"thread run");
16 int code=0;
17 sleep(1);
18 int ret=pthread_join(tid,NULL);
19 if(ret==0){
20 printf("thread wait success\n");
21 code=0;
22 }else{
23 printf("thread wait error\n");
24 code=1;
25 }
26 return code;
27 }
28
~
所以该程序的理论结果应该是输出thread wait error。
让我们来看看运行结果:
好了,这篇博客就先讲这么多,线程同步与互斥在下篇博客中呈现。