线程
为何要引入线程
多进程通讯的缺点有
- 创建进程的过程会带来一定的开销
- 为了完成进程间的数据交换,需要特殊的IPC技术
- 每秒少则数十次,多则数千次的 上下文切换 是创建进程时最大的开销。
上下文切换:运行程序前需要将相应的进程信息读入内存,如果运行进程A后需要紧接着运行进程B,就应该将进程A相关信息移出内存,并读入进程B相关信息。
而多线程是一种轻量级进程,它对比进程有如下的优点。
- 线程的创建和上下文切换比进程的创建和上下文切换更快。
- 线程间交换数据时无需特殊技术。
当然多线程肯定也是有缺点的:
- 每个线程与主程序共用地址空间,受限于2GB地址空间;
- 线程之间的同步和加锁控制比较麻烦;
- 一个线程的崩溃可能影响到整个程序的稳定性;
- 到达一定的线程数程度后,即使再增加CPU也无法提高性能,例如Windows Server 2003,大约是1500个左右的线程数就快到极限了(线程堆栈设定为1M),如果设定线程堆栈为2M,还达不到1500个线程总数;
- 线程能够提高的总性能有限,而且线程多了之后,线程本身的调度也是一个麻烦事儿,需要消耗较多的CPU
进程和线程的关系
每个进程都有 保存全局变量的数据区,向malloc等函数的动态分配提供空间的堆,函数运行时使用的栈构成。
可如果只需获得多个代码执行流为主要目的,这不应该创建如此多的进程结构,因为只需要分离站区域,因此在这里使用线程,结构如下:
采用线程会具有以下优势
- 上下文切换时不需要切换数据区和堆
- 可以利用数据区和堆交换数据
综上所述,可以总结进程和线程的联系。
- 进程:在操作系统构成单独执行流的单位。
- 线程:在进程构成单独执行流的单位。
下面给出操作系统,进程,线程的关系图解:
线程的资源共享
同一进程的线程以下资源共享
- 进程代码段
- 进程的公有数据(全局变量、静态变量…)
- 进程打开的文件描述符
- 进程的当前目录
- 信号处理器/信号处理函数:对收到的信号的处理方式
- 进程ID与进程组ID
线程独占哪些资源
- 线程ID
- 一组寄存器的值
- 线程自身的栈(堆是共享的)
- 错误返回码:线程可能会产生不同的错误返回码,一个线程的错误返回码不应该被其它线程修改;
- 信号掩码/信号屏蔽字(Signal mask):表示是否屏蔽/阻塞相应的信号(SIGKILL,SIGSTOP除外)
线程的创建及运行
线程的创建
线程具有单独的执行流,因此需要单独定义线程的main函数,还需要请求操作系统在单独的执行流中执行该函数。
#include <pthread.h>
int pthread_create(pthread_t * restrict thread, const pthread_attr_t * restrict attr, void * (* start_routine)(void *), void * restrict arg);
//成功时返回0,失败时返回其他值。
- thread 保存新创建的线程ID的变量地址值。
- attr 用于传递线程属性的参数,传递NULL是,创建默认属性的线程。
- start_routine 相当于线程main函数的,在单独执行流中执行的函数地址值(函数指针)。
- arg 通过第三个参数传递调用函数是包含传递参数信息的变量地址值。
要理解好以上参数,则必须运用实例,但是我们知道,线程会伴随着进程的终结而终结,所以说,假如说我们线程还没执行完,而进程就结束了,我们就不能达到线程所执行的效果,所以说,我们要引用一个新的函数,调用该函数的进程(或线程)将进入等待状态。知道第一个参数为ID的线程终止为止。
pthread_join函数
#include <pthread.h>
int pthread_join(pthread_t thread, void ** status);
//成功时返回0,失败时返回其他值
- thread 该参数值ID的线程终止后才会从该函数返回
- status 保存线程的main函数返回值的指针变量地址值。
此函数还能得到线程main函数的返回值到status,所以比较实用,下面给出应用实例。
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
void* thread_main(void *arg);
int main(int argc, char *argv[])
{
pthread_t t_id;
int thread_param = 5;
void * thr_ret;
if(pthread_create(&t_id, NULL, thread_main, (void*)&thread_param)!=0)
{
puts("pthread_create() error");
return -1;
}
if(pthread_join(t_id, &thr_ret)!=0)
{
puts("pthread_join() error");
return -1;
}
printf("Thread return message: %s \n", (char*)thr_ret);
free(thr_ret);
return 0;
}
void* thread_main(void *arg)
{
int i;
int cnt = *((int*)arg);
char * msg = (char*)malloc(sizeof(char)*50);
strcpy(msg, "Hello, I'am thread~ \n");
for(int i = 0;i < cnt;i++)
{
sleep(1);
puts("running thread");
}
return (void*)msg;
}
运行结果如下图:
程序大概逻辑图如下:
线程的基础概念大致就是这样了,之后会更新线程同步有关知识,有问题欢迎在评论区提出哦!