概述
进程与线程
进程
:
系统分配资源的基本单位
,
可以简单理解为一个正在进行的程序
线程
:
操作系统调度的最小单位
,
就是一段代码的执行顺序
注意:
1,
一个进程必须要有一个线程
,
该线程被称为主线程
2,
一个进程可以有多个线程
,
除主线程外的其他线程都是子线程
3,
进程被销毁时
,
其中的线程也将被销毁
.
4,
线程是轻量级的进程(
LWP
:
light weight process
),在
Linux
环境下线程的本
质仍是进程。
5,
进程所有线程都共享该进程的资源。
线程的特点
类
Unix
系统中,早期是没有
“
线程
”
概念的,
80
年代才引入,借助进程机制实现出了线程的概念。
因此在这类系统中,进程和线程关系密切:
1)
线程是轻量级进程
(light-weightprocess)
,也有
PCB
,创建线程使用的底层函数和进程一样,都是 clone
2)
从内核里看进程和线程是一样的,都有各自不同的
PCB.
3)
进程可以蜕变成线程
4)
在
linux
下,线程最是小的执行单位;进程是最小的分配资源单位
实际上,无论是创建进程的
fork
,还是创建线程的
pthreadcreate
,底层实现都是调用同一个内核函数 clone
。
Ø 如果复制对方的地址空间,那么就产出一个
“
进程
”
;
Ø 如果共享对方的地址空间,就产生一个
“
线程
”
。
Linux
内核是不区分进程和线程的
,
只在用户层面上进行区分。所以,线程所有操作函数pthread* 是库函数,而非系统调用
线程共享与非共享的资源
共享的
1)
文件描述符表
2)
每种信号的处理方式
3)
当前工作目录
4)
用户
ID
和组
ID
5)
内存地址空间
(.text/.data/.bss/heap/
共享库
)
非共享的
1)
线程
id
2)
处理器现场和栈指针
(
内核栈
)
3)
独立的栈空间
(
用户空间栈
)
4) errno
变量
5)
信号屏蔽字
6)
调度优先级
现成的优缺点
优点
提高程序并发性
开销小
数据通信、共享数据方便
缺点
库函数,不稳定
调试、编写困难、
gdb
不支持
对信号支持不好 优点相对突出,缺点均不是硬伤。
Linux
下由于实现方法导致进程、线程差别不是很大。
查看的指定的线程号(LWP)
ps - Lf pid
pid : 进程号
注意:
由于线程库原本不是系统本身的
,
所以在链接时需要手动链接库文件 ,编译源文件时输入gcc *.c -l pthread
线程相关函数
获取当前线程号
简述
线程号只在它所属的进程环境中有效。
线程号则用
pthread_t
数据类型来表示,
Linux
使用无符号长整数表示。
函数:
所需头文件
#include <pthread.h>
函数
pthread_t pthread_self(void);
功能:
获取线程号。
参数:
无
返回值:
调用线程的线程 ID
。
示例:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
int main()
{
pthread_t pthid = pthread_self();
printf("pthid=%ld\n",pthid);
return 0;
}
创建线程
函数
所需头文件
#include <pthread.h>
函数
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg );
参数:
thread:线程标识符地址。
attr:线程属性结构体地址,通常设置为
NULL
。
start_routine:线程函数的入口地址。
arg:传给线程函数的参数。
返回值:
成功:0
失败:非 0
示例:
#include <stdio.h>
#include <pthread.h>
// 注意线程调用的函数返回值为任意指针类型
void *myfunc01()
{
printf("线程%ld正在执行\n", pthread_self());
return NULL;
}
void *myfunc02(void *arg)
{
printf("线程%ld正在执行,参数为:%s\n", pthread_self(), (char *)arg);
return NULL;
}
int main(int argc, char const *argv[])
{
pthread_t p1, p2, p3;
pthread_create(&p1, NULL, myfunc01, NULL);
pthread_create(&p2, NULL, myfunc02, "Thread2");
pthread_create(&p3, NULL, myfunc02, "Thread3");
getchar();
return 0;
}
线程的回收
作用
等待线程结束(此函数会阻塞),并回收线程资源,类似进程的
wait()
函数。如果线程已经结束,那么该函数会立即返回。
函数
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
参数:
thread:被等待的线程号。
retval:用来存储线程退出状态的指针的地址
,
即回收的线程调用的函数的返回值
返回值:
成功:0
失败:非 0
示例:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *myfunc01(void *argv)
{
int *time = (int *)argv;
printf("线程A%ld将于%d秒后销毁\n", pthread_self(), *time);
sleep(*time);
return "线程A";
}
void *myfunc02(void *argv)
{
int *time = (int *)argv;
printf("线程B%ld将于%d秒后销毁\n", pthread_self(), *time);
sleep(*time);
return "线程B";
}
int main(int argc, char const *argv[])
{
int n01 = 3, n02 = 5;
pthread_t p1, p2;
pthread_create(&p1, NULL, myfunc01, &n01);
pthread_create(&p2, NULL, myfunc02, &n02);
void *argv01, *argv02;
pthread_join(p1, &argv01);
pthread_join(p2, &argv02);
printf("%s被回收\n", (char *)argv01);
printf("%s被回收\n", (char *)argv02);
return 0;
}
线程的分离
作用
使调用线程与当前进程分离,分离后不代表此线程不依赖与当前进程,线程分离的目的
是将线程资源的回收工作交由系统自动来完成,也就是说当被分离的线程结束之后,系
统会自动回收它的资源。所以,此函数不会阻塞。
函数
头文件
#include <pthread.h>
函数
void pthread_detach(patread_t thread);
参数:
thread:线程号
示例:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *fun1(void *arg)
{
printf("线程1开始执行\n");
sleep(3);
return NULL;
}
void *fun2(void *arg)
{
printf("线程2开始执行\n");
sleep(1);
return NULL;
}
int main(int argc, char const *argv[])
{
pthread_t tid1;
pthread_t tid2;
pthread_create(&tid1,NULL,fun1,NULL);
pthread_create(&tid2,NULL,fun2,NULL);
pthread_detach(tid1);
pthread_detach(tid2);
getchar();
return 0;
}
案例1:多线程遍历字符串
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
void *fun(void *arg)
{
printf("len = %ld\n",strlen(arg));
char *txt = (char *)arg;
char buf[32] = {0};
strcpy(buf,txt);
printf("线程%ld开始执行\n",pthread_self);
for(int i = 0;i < strlen(buf);i++)
{
printf("%c\n",buf[i]);
sleep(1);
}
printf("线程%ld执行结束\n");
}
int main(int argc, char const *argv[])
{
char buf[32] = {0};
while(1)
{
// memset(buf,0,32);
printf("请输入要打印的字符\n");
fgets(buf,32,stdin);
buf[strlen(buf)-1] = 0;
if(strcmp(buf,"886")==0)
{
break;
}
pthread_t t;
pthread_create(&t,NULL,fun,buf);
}
return 0;
}
线程的退出
作用
退出当前线程。一个进程中的多个线程是共享该进程的数据段,因此,通常线程退出后
所占用的资源并不会释放。
函数
所需头文件
#include <pthread.h>
函数
void pthread_exit(void *retval);
参数:
retval:存储线程退出状态的指针。
返回值:
无
示例
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *fun(void *arg)
{
for(int i = 0;i < 10;i++)
{
printf("%d\n",i);
sleep(1);
if(i == 3)
{
pthread_exit(NULL);
}
}
return NULL;
}
int main(int argc, char const *argv[])
{
pthread_t tid;
pthread_create(&tid,NULL,fun,NULL);
pthread_detach(tid);
return 0;
}
线程的取消
作用
退出指定线程
注意
:
线程的取消并不是实时的,而有一定的延时。需要等待线程到达某个取消点
(
检查点)
检测点:类似与游戏的存档,不是实时的,需要到特定的地方才会存档
函数
#include <pthread.h>
int pthread_cancel(pthread_t thread);
参数:
thread :目标线程id
返回值:
成功:0
失败:出错编号
示例:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *fun(void *arg)
{
for(int i = 0;i < 10;i++)
{
printf("%d\n",i);
sleep(1);
}
return NULL;
}
int main(int argc, char const *argv[])
{
pthread_t tid;
pthread_create(&tid,NULL,fun,NULL);
sleep(3);
pthread_detach(tid);
pthread_join(tid,NULL);
return 0;
}
线程的属性(了解)
概述
Linux
下线程的属性是可以根据实际项目需要,进行设置,之前我们讨论的线程都是采
用线程的默认属性,默认属性已经可以解决绝大多数开发时遇到的问题。
如我们对程序的性能提出更高的要求那么需要设置线程属性,比如可以通过设置线程栈
的大小来降低内存的使用,增加最大线程个数。
线程树形结构体
typedef struct
{
int
etachstate
;
//
线程的分离状态
int
schedpolicy
;
//
线程调度策略
struct
sched_param schedparam
;
//
线程的调度参数
int
inheritsched
;
//
线程的继承性
int
scope
;
//
线程的作用域
size_t
guardsize
;
//
线程栈末尾的警戒缓冲区大小
int
stackaddr_set
;
//
线程的栈设置
void *
stackaddr
;
//
线程栈最低地址
,
即线程栈的地址
,
默认是从所
属的进程的栈空间划分
size_t
stacksize
;
//
线程栈的大小
}
pthread_attr_t
;
注意:
1.
线程分离状态
2.
线程栈大小(默认平均分配)
3.
线程栈警戒缓冲区大小(位于栈末尾)
4.
线程栈最低地址
以上属性的属性值不能直接设置,须使用相关函数进行操作,初始化的函数为
pthread_attr_init
,这个函数必须在
pthread_create
函数之前调用。之后须用
pthread_attr_destroy
函数来释放资源。
线程属性相关函数
初始化
作用
:
初始化线程属性
函数
int pthread_attr_init(pthread_attr_t *attr);
返回值:
成功:0
;
失败:错误号
销毁
作用
销毁线程属性
函数
int pthread_attr_destroy(pthread_t *attr);
返回值:
成功:0
失败:错误号
分离状态
作用
:
设置分离状态
函数
int pthread_attr_setdetachstate(pthread_attr_t *attr,int detachstate);
参数:
attr:已初始化的线程属性
detachstate:
分离状态
PTHREAD_CREATE_DETACHED
(分离线程)
PTHREAD_CREATE_JOINABLE
(非分离线程)
作用
:
获取分离状态
int pthread_attr_getdetachstate(pthread_attr_t *attr, int
*detachstat);
参数
:
attr:已初始化的线程属性
detachstate:
分离状态
PTHREAD_CREATE_DETACHED
(分离线程)
PTHREAD_CREATE_JOINABLE
(非分离线程)
示例:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *fun(void *arg)
{
int i = 0;
while(1)
{
printf("%lu正在运行i=%d\n",pthread_self(),i++);
sleep(1);
}
}
int main(int argc, char const *argv[])
{
pthread_attr_t attr;
pthread_attr_init (&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);//设置分离属性
pthread_t tid;
pthread_create(&tid,NULL,fun,NULL);
printf("5秒后将结束线程\n");
sleep(5);
pthread_cancel(tid);
sleep(1);
printf("销毁线程\n");
pthread_attr_destroy(&attr);
return 0;
}
线程栈相关
概述:
//
设置栈的地址
int pthread_attr_setstack(pthreadattrt *attr, void *stackaddr, sizet stacksize);
参数:
attr:指向一个线程属性的指针
stackaddr:设置的栈的地址
stacksize:设置的栈的大小
返回值
成功:0 ;失败:错误号
成功:0 ;失败:错误号
//
得到栈的地址
int pthread_attr_getstack(pthreadattrt *attr, void **stackaddr, sizet*stacksize);
参数:
attr:指向一个线程属性的指针
stackaddr:返回获取的栈地址
stacksize:返回获取的栈大小
成功:
0
;失败:错误号
//
设置线程所使用的栈空间大小
int pthread_attr_setstacksize(pthreadattrt *attr, sizet stacksize);
参数:
attr:指向一个线程属性的指针
stacksize:设置的栈大小
成功:
0
;失败:错误号
//
得到线程所使用的栈空间大小
int pthread_attr_getstacksize(pthreadattrt*attr, sizet *stacksize);
参数:
attr:指向一个线程属性的指针
stacksize:获取线程的栈大小
成功:
0
;失败:错误号
示例:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <
void *fun(void *arg)
{
int i = 0;
while(1)
{
printf("%lu线程正在执行i=%d\n",pthread_self(),i++);
sleep(1);
}
return NULL;
}
int main(int argc, char const *argv[])
{
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
void *stackaddr = calloc(1,128);
if(stackaddr == NULL)
{
perror("null");
return 0;
}
pthread_attr_setstack(&attr,&stackaddr,128);
pthread_t tid;
printf("线程在5秒后结束\n");
pthread_create(&tid,&attr,fun,NULL);
sleep(5);
pthread_cancel(tid);
sleep(1);
pthread_attr_destroy(&attr);
free(stackaddr);
return 0;
}
注意事项
1)
主线程退出其他线程不退出,主线程应调用
pthread_exit
2)
避免僵尸线程
a) pthread_join
b) pthread_detach
c) pthread_create 指定分离属性
被
join
的线程可能在
join
函数返回前就释放完自己的所有内存资源,所以不应当返回被回收线程栈中的值; 3) malloc
和
mmap
申请的内存可以被其他线程释放
4)
应避免在多线程模型中调用
fork
,除非马上
exec
,子进程中只有调用
fork
的线程存在,其他线程 t
在子进程中均
pthread_exit
5)信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制
案例
火车票售票问题
定义一个记录火车票的剩余数量
4
个窗口同时销售该火车票
代码:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
int ticket = 100;
void *sale(void *name)
{
char buf[32] = {0};
strcpy(buf,(char*) name);
while(ticket > 0)
{
ticket--;
sleep(0.5);
printf("%s售卖了一张船票,还剩%d张船票\n",buf,ticket);
}
}
int main(int argc, char const *argv[])
{
pthread_t tid1,tid2,tid3,tid4;
pthread_create(&tid1,NULL,sale,"一号窗口");
pthread_create(&tid2,NULL,sale,"二号窗口");
pthread_create(&tid3,NULL,sale,"三号窗口");
pthread_create(&tid3,NULL,sale,"四号窗口");
pthread_detach(tid1);
pthread_detach(tid2);
pthread_detach(tid3);
pthread_detach(tid4);
getchar();
return 0;
}
作业
1,
整理笔记
2,
完成以下情况
一个线程打印1~52
一个线程打印a~z
3,
使用代码模拟龟兔赛跑
乌龟每秒1
米
兔子每秒5
米
赛程100
米
兔子在临近终点睡觉了,
导致乌龟赢了
4,
完成售票案例
,
查看并分析结果
,
说明原因
代码参考上面案例
原因:
是因为卖了第一张票后,其他三个窗口在抢cpu执行权,在第一张票卖出去,提示的信息还未执行的时候,其他三个线程有一个线程有一个将cpu执行权抢了过去,这是票数实际是99,同理以此类推,其他线程将cpu执行权抢了过去,所以将卖了四张票后才第一次打印提示信息,这时实际票数已经是96张,所以,这时提示的信息中将之前的四次卖票纪律一次性打出来了,所以都是剩余96张。总之,这是个异步,在栈中,每个线程都将有各自独立的ticket,都是100张。
解决办法:
是要解决其中异步的问题,所以要解决这一问题,就是要实现同步,让四个线程只对一个ticket操作就可以。
简单的可以用将ticket设置为全局变量