1.线程概念
- 在一个程序里的一个执行路线叫做线程。更准确的定义:线程是一个进程内部的控制序列
- 一个进程至少都有一个执行线程
- 线程在进程内部运行,本质是在进程地址空间内运行
- 在linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化
- 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程合理分配给每个执行流,就形成了线程执行流
- 创建一个新线程的代价要比一个新进程小的多
- 与进程之间的切换相比,线程之间的切换需要操作系统的工作少的多
- 线程占用的资源要比进程少很多
- 能充分利用多处理器的可并行数量
- 在等待慢速I / O操作结束的同时,程序可执行其他的计算任务
- 性能损失
损失是指增加了额外的同步和调度开销,而可用的资源不变 - 健壮性降低
在时间分配上的细微差别或者因共享了不该共享的变量而造成了不良影响的可能性很大 - 缺乏访问控制
进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响 - 编程难度提高
编写与调试一个多进程程序比单线程程序困难的多
- 单个线程如果出现除零,野指针问题导致线程崩溃,也会导致进程崩溃
- 线程是进程分支,线程出现异常,进而触发信号机制
- 相关命令
pstack [pid]:查看进程当中各个线程的执行调用堆栈
top “1”:可以查看各个CPU的负载
-H -p [pid] :查看各个线程的工作状态
- 合理的使用多线程,能提高CPU密集型程序的执行效率
- 合理的使用多线程,能提高IO密集型程序的用户体验
2.进程和线程的区别
- 进程是资源分配的基本单位
- 线程是调度的基本单位
- 线程共享内存进程数据,但也拥有自己的数据:线程ID、一组寄存器、栈、error、信号屏蔽字、调度优先级
- 统一地址空间,因此Text Segment、Data Segment都是共享的
- 如果定义一个全局变量,在个线程中都可以访问到,除此之外,各线程还共享的资源和环境:文件描述符、每种信号的处理方式(SIG_IGN、SIG_DFL或者自定义的信号处理函数)、当前工作目录、用户id和组id
3.Linux线程控制
- 大多数函数都是以"pthread_"打头的
- 头文件**<pthread.h>**
- 链接这些线程函数库要加编译器命令"-lpthread"
- 临时变量
① 临时变量的声明周期
② 临时变量的值的改变
③ 传递临时变量有可能导致越界的问题
注意:不建议传递临时变量 - 结构体对象:参考临时变量
- 结构体指针可以
// 测试线程入口函数可以传递结构体指针
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
struct Data{
int data_;
};
void* myStart(void* arg)
{
Data* d = (Data*)arg;
while(1)
{
printf(" workspace :%p, i = %d\n", pthread_self(), d->data_);
sleep(1);
}
delete d;
d = NULL;
return NULL;
}
int main()
{
pthread_t tid;
for(int i = 0; i < 4; i++)
{
Data* d = new Data();
d->data_= i;
int ret = pthread_create(&tid, NULL, myStart, (void*)d);
if(ret < 0)
{
perror("pthread error!\n");
return -1;
}
}
while(1)
{
printf("i am main pthread!\n");
sleep(1);
}
return 0;
}
- 创建线程
- int pthead_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
pthread_t:线程标识符,是一个出参
pthread_attr_t:线程属性(栈的大小,一般不设置,传值NULL)
start_routine:本质是函数指针,保存线程入口函数的地址
arg:给线程入口函数的传参 - 返回值:
创建失败:小于0
创建成功:等于0
- 线程终止
- 从入口函数的return返回,改线程就退出掉了
- void pthread_exit(void retval);
retval:返回信息,可以给也可以不给,是返回给等待线程对出的执行流的,如果不给,则传递NULL - int pthread_cancel(pthread_t thread);
thread:线程标识符
调用该函数的执行流可以取消其他线程,但是需要知道其他线程的线程标识符,也可以执行流自己取消自己,传入自己的线程标识符(获取自己的线程标识符:pthread_self() ) - 注意:线程在创建出来的时候,属性当中默认是joinable属性(意味着线程在退出的时候需要其他执行流来回收线程的资源)
- 线程等待
- int pthread_join(pthread_t thread, void **retval);
thread:要等待的线程的标识符
retval:
return:接收入口函数的返回值
pthread_exit:接收pthread_exit函数的参数
pthread_cancel:void *保存一个常数PTHREAD_CANCELED (内核:#define PTHREAD_CANCELED (void *)(-1)) - 注意:调用该函数的执行流在等待线程退出的时候,该执行流是阻塞在pthread_join函数当中
- 线程分离
- 作用:改变线程的属性将joinable属性改变成为detach属性,当线程退出的时候,不需要其他线程来回收退出线程的资源,操作系统会默认回收掉
- int pthread_detach(pthread_t thread);
thread:想要被分离线程的线程标识符
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#define PTHREADCOUNT 1
void* myStart(void* arg)
{
pthread_detach(pthread_self());
while(1)
{
sleep(20);
printf("i am workspace\n");
pthread_exit(NULL);
}
#if 0
pthread_cancel(pthread_self());
printf("i am workspace!\n");
#endif
return NULL;
}
int main()
{
pthread_t tid[PTHREADCOUNT];
for(int i = 0; i < PTHREADCOUNT; i++)
{
int ret = pthread_create(&tid[i], NULL, myStart, NULL);
if(ret < 0)
{
printf("create error!\n");
return -1;
}
}
for(int i = 0; i < PTHREADCOUNT; i++)
{
// 线程等待
pthread_join(tid[i], NULL);
// 线程分离
pthread_detach(tid[i]);
}
return 0;
}