线程,我自己看到的两篇讲解比较生动形象的易于理解的博文,如下。
- 线程与进程之间的关系:
http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html - 对线程的理解(一篇比较生动形象的讲解):
http://mp.weixin.qq.com/s?__biz=MjM5NzA1MTcyMA==&mid=403498894&idx=2&sn=219c1a6001b5bb7e6bdc7963b1af8450&scene=2&srcid=0330UDNmQ
linux操作系统是支持多线程的,它在一个进程内生成了许多个线程。一个进程可以拥有一至多个线程。多线程相对于多进程还是有不少优点的:
- 在多进程的情况下,每个进程都有自己独立的地址空间,而在多线程的情况下,同一进程内的的线程共享进程的地址空间。因此,创建一个新的进程时就要耗费时间来为其分配系统资源,而创建一个新的线程花费的时间就很少。
- 在系统调度方面,由于进程地址空间独立而线程共享地址空间,线程间的切换速度要远远快过进程间的切换速度。
- 在通信机制方面,进程间的数据空间相互独立,彼此通信要以专门的通信方式进行,通信时必须经过操作系统。而同一进程内的多个线程共享数据空间,一个线程的数据可以直接提供给其他线程使用,而不必经过操作系统。所以,线程间的通信更加方便和省时。
- 可以提高应用程序的响应速度。
- 可以提高多处理器的效率。
- 可以改善程序的结构。对于要处理多个命令的应用程序,可以对每个命令的处理设计成一个线程,从而避免设计成大程序时造成的程序结构复杂。
虽然线程在进程内共享地址空间,打开的文件描述符等资源。但是线程也有其私有的数据信息。
- 线程号:每个线程都有一个唯一的线程号一一对应。
- 寄存器
- 堆栈
- 信号掩码
- 优先级
线程私有的存储空间
注意:编写linux下的多线程应用程序,需要头文件pthread.h,编译时需要加上-lpthread
1、创建线程:
线程的创建通过函数pthread_creat来完成。
(1) 该函数声明及头文件如下:
#include<pthread.h>
int pthread_create(pthread_t *thread,pthread_attr_t *attr,
(void*)(*start_routine)(void*),void *arg);
编译链接参数
-lpthread
(2)返回值
若线程创建成功,则返回0。若线程创建失败,则返回出错编号。
返回成功时,由thread指向的内存单元被设置为新创建线程的线程ID。attr参数用于指定各种不同的线程属性。新创建的线程从start_routine函数的地址开始运行,该函数只有一个万能指针参数arg,如果需要向start_routine函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg的参数传入。
- thread:该参数为指向线程标识符的指针,当线程创建成功时,用来返回创建的线程id
- attr:该参数用来指定线程属性,NULL表示使用默认属性。
- start_routine:该参数是一个函数指针,指向线程创建后要调用的函数。
- arg:该参数指向传递该线程函数的参数。
(3)第二个参数是一个指向pthread_attr_t结构体(线程属性结构)的指针,该结构体如下:
typedef struct
{
int detachstate; // 线程的分离状态
int schedpolicy; //线程调度策略
struct sched_param schedparam; //线程的调度参数
int inheritsched; // 线程的继承性
int scope; //线程的作用域
size_t guardsize; //线程栈末尾的警戒缓冲区大小
int stackaddr_set;//堆栈地址集
void * stackaddr; // 线程栈的位置
size_t stacksize; // 线程栈的大小
}pthread_attr_t;
(4)创建线程这里还会用到的几个系统函数:
pthread_t pthread_self(void) //获取本线程的id
int pthread_equal(pthread_t thread1,pthread_t thread2) //判断两个线程id是否指向同一个线程
int pthread_once(pthread_once_t * once_control,void(*init_routine)(void)) //用来保证init_routine线程函数在进程中仅执行一次
下面为一个创建线程的例子
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
int * thread(void * arg)
{
pthread_t newthid;
newthid = pthread_self();
printf("this is a new thread,thread ID = %lu\n",newthid);
return NULL;
}
int main(void)
{
pthread_t thid;
printf("main thread,ID is %lu\n",pthread_self()); //pthread_self()获取本线程id。
if(pthread_create(&thid,NULL,(void *)thread,NULL)!=0) //创建线程
{
printf("thread creation failed\n");
exit(1);
}
sleep(1);
exit(0);
}
运行结果:
先打印出主线程的ID,然后打印出新创建的线程ID。
main thread,ID is 140169071576896
this is a new thread,thread ID = 140169062905600
有时我们想在多线程的情况下,让某些函数只执行一次,该怎么办?
这时就要使用函数pthread_once
示例:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
pthread_once_t once = PTHREAD_ONCE_INIT;
void run(void)
{
printf("Founction run is running in thread %lu\n",pthread_self());
}
void * thread1(void *arg)
{
pthread_t thid=pthread_self();
printf("当前的线程ID 是 %lu\n",thid);
pthread_once(&once,run);
printf("thread1 结束\n");
}
void * thread2(void * arg)
{
pthread_t thid=pthread_self();
printf("当前的线程 ID 是 %lu\n",thid);
pthread_once(&once,run);
printf("thread2 结束\n");
}
int main()
{
pthread_t thid1,thid2;
//创建线程
pthread_create(&thid1,NULL,thread1,NULL);
pthread_create(&thid2,NULL,thread2,NULL);
sleep(3);
printf("主线程退出!\n");
exit(0);
}
运行结果:
当前的线程 ID 是 140685518632704
Founction run is running in thread 140685518632704
thread1 结束
当前的线程 ID 是 140685510240000
thread2 结束
主线程退出!
我们可以看到两个线程都调用了run函数,但函数run只在线程thread1
中运行了一次。
在多线程编程环境下,尽管pthread_once()调用会出现在多个线程中,init_routine()函数进执行一次,究竟在哪个线程中执行是不定的,是由内核调度来决定的
2、线程终止
linux下有两种方式可以使线程终止,第一种通过return
从线程函数返回
第二种是调用函数pthread_exit()
使线程退出。
pthread_exit头文件及其原型:
#include<pthread.h>
void pthread_exit(void * retval)
这里要注意两种特殊情况:
- 在主线程中,如果从main函数返回或是调用了exit函数退出主线程,则整个进程将终止,此时进程中所有线程也将终止,因此在主线程中不能过早地从main函数返回。
- 如果主线程调用pthread_exit函数,则仅仅是主线程消亡,进程不会结束,进程内的其他线程也不会终止,直到所有线程结束,进程才会结束。
(1)线程终止会带带来资源释放问题:特别是临界资源,临界资源在一段时间内只能被一个资源所持有,当线程要使用临界资源时需提出申请,如果该资源未被使用,则申请成功,否则等待。
临界资源被一个线程所独占,当一个线程终止时,如果不释放其占有的临界资源,则该资源会被认为还被已退出的线程所使用,因而永远不会得到释放。如果一个线程在等待使用这个临界资源,它就有可能无限的等下去,这就形成了死锁。再难也就因此而来。
为此,系统提供了一些函数,用于自动释放资源,从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(如调用pthread_exit)都将执行pthread_cleanu_push()所指定的清理函数
pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式实现的,这是pthread.h中的宏定义:
#define pthread_cleanup_push(routine,arg)
{ struct _pthread_cleanup_buffer _buffer;
_pthread_cleanup_push (&_buffer, (routine), (arg));
#define pthread_cleanup_pop(execute)
_pthread_cleanup_pop (&_buffer, (execute)); }
pthread_cleanup_push()带有一个"{",而pthread_cleanup_pop()带有一个"}",因此这两个函数必须成对出现,且必须位于程序的同一级别的代码段中才能通过编译.
(2)线程终止还要注意线程间的同步问题,因为进程间各个现成的运行是相互独立的,线程的终止是不会相互通知的,也不会互相影响的,终止的线程所占用的资源并不会随着线程的终止而归还系统,而是仍归线程所在的进程持有,同样,对于这种情况,我们可以利用下面两个函数来解决问题。
#include<pthread.h>
void pthread_exit(void * retval);
int pathread_join(pthread_t th,void * thread_return);
int pthread_detach(pthread_t th);
- pthread_join:以阻塞方式等待thred指定的线程结束。只有线程是可接入的时候,调用pthread_join才会成功,
其中thrad:线程标识符,即线程ID,retval:用户定义的指针,用来存储被等待线程的返回值。
- pthread_detach,这个函数可以改变线程的可接入属性,将其改变成分离模式。当线程处于分离模式,表明我们对于线程的返回值并不关心,工作线程执行完毕后会自行退出,内核会释放该线程占用的资源。
一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join()的线程返回错误代码ESRCH.
以下为主线程通过pthread_join等待铺主线程结束示例
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
void assisthread(void * arg)
{
printf("I am helping to do some jobs\n");
sleep(3);
pthread_exit(0);
}
int main(void)
{
pthread_t assistthid;
int status;
pthread_create(&assistthid,NULL,(void *)assisthread,NULL);//创建线程
pthread_join(assistthid,(void *) &status); //阻塞主线程。等待线程aassistthid结束
printf("assistthread's exit is caused %d\n",status);
return 0;
}
运行结果:
I am helping to do some jobs
assistthread's exit is caused 0
辅助线程线程的退出码为0,pthread_join得出的status也为0,一致。