一、概念
**线程:**线程是一个轻量级的进程
二、线程的创建
1、线程的空间
(1)进程的空间包括:系统数据段、数据段、文本段
(2) 线程位于进程空间内部
(3) 栈区独享、与进程共享文本段、数据段、堆区

2、线程与进程的关系
**进程:**进程是操作系统资源分配的最小单元(进程:文本段+数据段+系统数据段)
**线程:**线程是CPU任务调度的最小单元
(1)每个线程独立拥有8M(默认)栈空间
(2)其余的文本段、数据段、堆区都是与进程及进程内的其余线程共享的
三、线程的调度
(1)线程的调度等价于进程的调度(宏观并行,微观串行)
线程之间宏观并行,微观串行;所以线程在执行的过程中,会出现以下情况:
情况一:线程1先执行,线程二后执行
情况二:线程2先执行,线程1后执行
情况三:线程2执行一半后执行线程1,线程1执行一半后执行线程2,即二者交叉执行
(2)线程是CPU任务调度的最小单元
四、线程的消亡
(1)与进程消亡保持一致,进程结束时不论线程处于什么状态,线程被强制结束
(2)僵尸线程:等到线程结束需要回收线程空间,否则会产生僵尸线程
五、多进程vs多线程
1、执行效率
多线程 > 多进程
线程间任务的切换是在同一片进程空间内部完成任务切换,资源开销小
进程间任务的切换需要映射到不同的物理地址空间,频繁切换资源开销大
2、多任务间通信的实现
多线程 > 多进程
多线程拥有共享空间(多个线程位于一个进程内,共享该进程的数据区、文本区),通信更加方便
多进程没有共享空间(进程间是独立的),需要更为复杂的方法实现多进程间通信
3、多任务通信机制的复杂程度
多线程 > 多进程
多线程操作全局变量会引入资源竞争,需要加锁来解决
多进程没有资源竞争
4、安全性
多进程 > 多线程
线程异常崩溃导致进程崩溃,该进程中的其余线程异常结束
进程异常结束,不会影响其余进程
六、线程相关的函数接口
pthread_create == fork
pthread_exit == exit
pthread_join == wait
1、pthread_creat
man 3 pthread_create
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
功能:
在调用函数的进程中创建一个线程
参数:
thread:存放线程ID空间的首地址
attr:线程的属性
start_routine:线程函数的入口
void *(*start_routine) (void *)函数指针,返回值类型void*,参数void*,*start_routine:函数首地址(函数名本身就是函数首地址)
arg:传入线程函数的参数(给start_routine函数传的参数)
返回值:
成功返回0
失败返回错误数字
#include <stdio.h>
#include "public.h" //头文件中包含pthread_creat的头文件,但是编译时还需使用 -lpthread(链接头文件)
//cc src/main.c -o ./build/a.out -I./include -lpthread
void *thread_fun(void *arg)
{
printf("线程(TID:%#x)开始执行\n", (unsigned int)pthread_self()); //pthread_self获取线程ID
return NULL;
}
int main(int argc, const char **argv)
{
pthread_t tid1;
pthread_t tid2;
pthread_t tid3;
int ret = 0;
ret = pthread_create(&tid1, NULL, thread_fun, NULL);
if (ret != 0)
{
ERR_MSG("fail to pthread_create");
return -1;
}
ret = pthread_create(&tid2, NULL, thread_fun, NULL);
if (ret != 0)
{
ERR_MSG("fail to pthread_create");
return -1;
}
ret = pthread_create(&tid3, NULL, thread_fun, NULL);
if (ret != 0)
{
ERR_MSG("fail to pthread_create");
return -1;
}
while (1)
{
}
return 0;
}
2、pthread_exit
man 3 pthread_exit
void pthread_exit(void *retval);
功能:
线程退出
参数:
retval:线程退出的值
返回值:
缺省
3、pthread_join
man 3 pthread_join
int pthread_join(pthread_t thread, void **retval);
功能:
回收线程空间
参数:
thread:要回收的线程的ID
retval:存放线程结束状态指针空间的首地址
返回值:
成功返回0
失败返回错误码
注意:
1.pthread_join具有阻塞功能,线程不结束,一直等到线程结束,回收到线程空间再继续向下执 行
2.pthread_join具有同步功能
#include <stdio.h>
#include "public.h"
#if 0
int main(int argc,const char** argv)
{
pthread_t tid1;
pthread_t tid2;
pthread_t tid3;
pthread_create(&tid1, NULL, thread1, NULL);
pthread_create(&tid2, NULL, thread2, NULL);
pthread_create(&tid3, NULL, thread3, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
return 0;
}
#endif
#if 0
void *thread1(void *arg)
{
printf("线程1(TID:%#lx)正在执行\n", pthread_self());
return NULL;
}
void *thread2(void *arg)
{
printf("线程2(TID:%#lx)正在执行\n", pthread_self());
return NULL;
}
void *thread3(void *arg)
{
printf("线程3(TID:%#lx)正在执行\n", pthread_self());
return NULL;
}
int main(int argc, const char **argv)
{
pthread_t tid[3];
int i = 0;
int a[3] = {1, 2, 3};
void *(*pfun[3])(void *) = {thread1, thread2, thread3}; //函数指针数组
for (i = 0; i < 3; i++)
{
pthread_create(&tid[i], NULL,pfun[i],NULL);
}
for (i = 0; i < 3; i++)
{
pthread_join(tid[i], NULL);
}
#endif
#if 1
void *thread(void* arg)
{
int* pnum = arg;
printf("线程%d(TID:%#lx)正在执行\n", *pnum, pthread_self());
return NULL;
}
int main(int argc, const char **argv)
{
pthread_t tid[3];
int i = 0;
int a[3] = {1, 2, 3};
for (i = 0; i < 3; i++)
{
//pthread_create(&tid[i], NULL,thread,&i); //error 所有线程共享同一个 i 的地址,因此当线程函数 thread 访问 &i 时,获取的值可能是循环结束后 i 的最终值(即 3),而不是创建线程时的值。这会导致所有线程可能接收到相同的值3,而不是预期的 0、1、2
pthread_create(&tid[i], NULL,thread,&a[i]); //right 数组 a 的值是固定的({1, 2, 3}),每个线程会接收到数组 a 中对应索引的值。由于每个线程接收的是数组 a 中不同元素的地址,线程函数 thread 可以正确地访问到 1、2、3 这些值
}
for (i = 0; i < 3; i++)
{
pthread_join(tid[i], NULL);
}
#endif
return 0;
}
七、线程的分离属性
1、概念
线程结束后,操作系统自动回收线程空间,无需调用pthread_join来回收线程空间
2、函数接口
man 3 pthread_attr_setdetachstate
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
功能:
将线程属性中加入分离属性
在这种状态下,线程终止时会自动释放其资源,无需其他线程调用 pthread_join()
参数:
attr:线程属性空间首地址
detachstate: PTHREAD_CREATE_DETACHED 分离属性 PTHREAD_CREATE_JOINABLE 加入属性
返回值:
成功返回0
失败返回错误码
pthread_attr_setdetachstate需要用到的辅助函数//对attr进行初始化
int pthread_attr_init(pthread_attr_t *attr);
//销毁attr
int pthread_attr_destroy(pthread_attr_t *attr);
#include <stdio.h>
#include "public.h"
#if 1
int is_exit = 0;
void* thread1(void* arg)
{
int cnt = 0 ;
while(!is_exit)
{
if(cnt == 2)
{
printf("采集线程正在执行!\n");
cnt = 0;
}
cnt++;
usleep(500000);
}
return NULL;
}
void* thread2(void* arg)
{
int cnt = 0 ;
while(!is_exit)
{
if(cnt == 4)
{
printf("显示线程正在执行!\n");
cnt = 0;
}
cnt++;
usleep(500000);
}
return NULL;
}
void* thread3(void* arg)
{
int cnt = 0 ;
while(!is_exit)
{
if(cnt == 10)
{
printf("存储线程正在执行!\n");
cnt = 0;
}
cnt++;
usleep(500000);
}
return NULL;
}
void* thread4(void* arg)
{
int cnt = 0 ;
while(!is_exit)
{
if(cnt == 20)
{
printf("日志线程正在执行!\n");
cnt = 0;
}
cnt++;
usleep(500000); //同样是睡眠10秒但这样的操作让单位时间缩短了,避免了线程正在睡眠时线程结束的情况
//sleep(10)
}
return NULL;
}
int main(int argc,const char** argv)
{
int i = 0;
pthread_t tid[4];
pthread_attr_t attr;
void*(*pfun[4])(void*) = {thread1,thread2,thread3,thread4};
char tmpbuff[1024] = {0};
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
for(i = 0;i < 4;i++)
{
pthread_create(&tid[i],&attr, pfun[i], NULL);
}
pthread_attr_destroy(&attr);
while(1)
{
gets(tmpbuff);
if(0 == strcmp(tmpbuff,".quit"))
{
is_exit = 1;
break;
}
}
sleep(1); //进程睡眠1秒,确保线程完全执行过
return 0;
}
#endif