一、线程的概念
1、进程
进程就是正在系统中运行的程序。
程序被运行时,会被系统从硬盘加载到内存中,进程会在内存中形成一个独立的内存体,这个内存体有自己的堆,栈,数据段,代码段等资源。进程的上级挂靠操作系统。
进程是系统中资源分配的最小单位!
2、线程
线程是比进程更小的单位,在操作系统中不能独立存在,线程必须要存在于进程中。一个进程里面至少有一条线程(主线程),线程是系统中任务调度的最小单位,简单来说,线程就是用来干活的。
线程是系统中任务调度的最小单位!
操作系统: 一家公司
进程:公司里面的部门
线程:部门里面的员工
主线程:部门的主管
子线程:部门里的普通员工
3、进程与线程
1)资源
进程: 每一个进程都有自己独立的资源(堆,栈,数据段,代码段);
线程: 同一个进程里面线程共享绝大部分的资源,线程只有自己独立的一部分必不可少的资源(栈)
2)地位
进程是系统中 资源分配 的最小单位!
线程是系统中 任务调度 的最小单位!
线程是存在于进程内部的,如果进程被结束,那么这个进程里面的所有线程都会被结束。
3)关系
一个进程至少有一条线程,可能存在多条线程。在实际应用开发中,进程在创建于撤销时需要比较大的系统开销,所以可以选择使用多线程,线程的系统开销比较小(独立的栈空间)
二、相关API
1、Pthread_create() //创建一条线程
SYNOPSIS
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
Compile and link with -pthread.
==> thread : 存放线程ID的地址
==> attr : 线程属性 (分离,不分离) NULL
==> start_routine : 参数为void *,返回值为void *类型的函数指针
==> arg : 第三个参数的参数
返回值: 成功返回0,失败返回非0的数.
在编译代码的时候添加 -pthread
例子1:
设计程序,创建一条子线程,主线程每隔1s打印一个hello,子线程每隔1s打印一个world
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
void *reutine(void *arg)
{
for(; ;)
{
printf("hello\n");
sleep(1);//延时1S
}
}
int main(int argc,char *argv[])
{
pthread_t tid;
pthread_create(&tid,NULL,reutine,NULL);//创建线程
for( ; ; )
{
printf("world\n");
sleep(1);
}
return 0;
}
运行结果:
例子2:
参考示例代码,设计程序,创建一条子线程,子进程循环输出时间,主线程每隔2s输出一句”helloworld”,10s之后主线程退出。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
void *reutine(void *arg)
{
for(int i = 1; ;i++)
{
printf("time: %d\n",i);
sleep(1);//延时1S
}
}
int main(int argc,char *argv[])
{
pthread_t tid;
pthread_create(&tid,NULL,reutine,NULL);//创建线程
for(int i=0; i<5 ;i++)
{
printf("hello world\n");
sleep(2);
}
return 0;
}
注意: 一个进程中线程之间的关系
1) 线程共享进程之间的绝大部分资源(除了栈)
2) 线程与线程之间没有先后之分(并发执行)
3) 只要进程结束,进程中的所有线程都会结束(主线程结束 --> 所有线程都结束)
例子3:
实现线程传参功能。进程创建一条子线程,给子线程传递一个整数n,子线程接收到这个整数n,在n秒之后结束进程,主线程循环打印时间。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
void *routine(void *arg)
{
int n = *(int *)arg;
printf("[%d]传递的参数是:%d\n",getpid(), n);
sleep(n);
exit(0); //结束进程
}
/*线程传参*/
int main(int argc, char *argv[])
{
int n = 8;
//1,创建子线程
pthread_t tid;
pthread_create(&tid, NULL, routine, (void *)&n);//pthread_create函数第4个参数为 void *arg类型,所以要强转
//2,主线程循环打印时间
for(int i=1; ;i++)
{
printf("[%d]:time:%d\n", getpid(), i);
sleep(1);
}
return 0;
}
//=======================================================//
线程退出的几种情况:
A.线程执行函数代码执行完毕。
B.线程执行函数中执行return
C.线程执行函数中执行 pthread_exit(); 线程退出函数
D.线程被其他的线程发送取消请求,线程取消。 pthread_cancel()
E.进程退出,所有线程被强制关闭。
//=======================================================//
2、pthread_exit(); //线程退出函数
SYNOPSIS
#include <pthread.h>
void pthread_exit(void *retval );
==>retval:返回线程退出的状态值, void * //这个参数不能是栈空间里面的变量
线程中不能使用exit函数,这会导致整个进程退出
3、pthread_join(); //线程资源回收函数
SYNOPSIS
#include <pthread.h>
int pthread_join(pthread_t thread,void **retval);
==>thread: 线程的ID
==>retval: 子线程结束状态
例子4:
设计程序,创建一条子线程,子线程循环打印时间,10s之后结束,主线程等待回收子线程的资源,输出子线程的退出状态。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
void *routine(void *arg)
{
int *ret = (int *)malloc(sizeof(int));
*ret = 10;
for(int i=0; i<10; i++)
{
printf("time:%d\n", i);
sleep(1);
}
pthread_exit((void *)ret);
}
/*主线程回收子线程资源*/
int main(int argc, char *argv[])
{
//1,创建子线程
pthread_t tid;
pthread_create(&tid, NULL, routine, NULL);
//2,主线程等待回收资源
void *status;
pthread_join(tid, &status); //阻塞等待
printf("子线程退出状态:%d\n", *(int *)status);
return 0;
}
三、线程的分离
线程在被创建出来后,会得到一片属于自己的空间(栈空间),在线程结束之后,需要其他线程调用pthread_join() 去结合线程,回收线程资源。但是如果调用这个函数就会造成其他线程阻塞,如何实现线程资源的自动回收呢?
==> 将线程的属性设置成分离属性,线程结束之后资源就会被自动回收。
4、pthread_detach() //线程分离属性设置
SYNOPSIS
#include <pthread.h>
int pthread_detach(pthread_t thread);
==>thread:需要分离的线程ID
注意:如果在前面调用了 pthread_detach() 设置了线程分离,那么就不能调用 pthread_join函数去等待回收子线程的资源。
例子5:
线程分离属性的设置
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
void *routine(void *arg)
{
for(int i=0; i<10; i++)
{
printf("time:%d\n", i);
sleep(1);
}
}
/*线程分离属性*/
int main(int argc, char *argv[])
{
//1,创建子线程
pthread_t tid;
pthread_create(&tid, NULL, routine, NULL);
//设置线程分离属性
pthread_detach(tid);
//2,主线程
sleep(2);
pthread_join(tid, NULL); //如果前面设置了线程分离,那么这个函数就不再阻塞等待,直接结束!
return 0;
}
线程创建时设置分离属性:
对线程创建函数的第二个参数进行设置
Step1:定义一个线程属性变量 attr ==> pthread_attr_t attr;
Step2: 对线程属性变量初始化 pthread_attr_init()
SYNOPSIS
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
Step3: 设置线程分离属性 :pthread_attr_setdetachstate
SYNOPSIS
#include <pthread.h>
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
==> attr : 线程属性变量地址
==> detachstate : 线程分离属性
PTHREAD_CREATE_DETACHED (分离)
PTHREAD_CREATE_JOINABLE (不分离)
四、线程的取消
在实际应用中,如何把一个正在运行的线程结束?
线程取消 pthread_cancel();
这个函数的功能是给线程发送一个取消请求,线程会根据自身状态来决定是否结束线程。
5、线程取消函数 pthread_cancel()
SYNOPSIS
#include <pthread.h>
int pthread_cancel(pthread_t thread);
==> thread : 需要取消的线程的ID
线程取消响应属性设置:
响应取消
立即响应取消(立即退出线程)
延时响应(在退出节点退出线程)
不响应取消
线程对于线程取消请求默认时 延时响应。
SYNOPSIS
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate); //设置是否响应取消
int pthread_setcanceltype(int type, int *oldtype); //设置取消类型
==> state : 是否响应取消状态
PTHREAD_CANCEL_ENABLE : 响应
PTHREAD_CANCEL_DISABLE : 不响应
==> oldstate : 保存原始状态下是否响应取消信号的变量指针
==> type : 响应类型
PTHREAD_CANCEL_DEFERRED : 延时响应
PTHREAD_CANCEL_ASYNCHRONOUS : 立即响应
==> oldtype : 原来的响应类型的变量的地址
例子6:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
void *routine(void *arg)
{
//设置子线程为不响应取消请求类型
int oldstate;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
for(int i=0; i<10; i++)
{
printf("time:%d\n", i);
sleep(1);
}
}
/*线程取消*/
int main(int argc, char *argv[])
{
//1,创建子线程
pthread_t tid;
pthread_create(&tid, NULL, routine, NULL);
sleep(2);
//发送取消请求
pthread_cancel(tid);
printf("send cancel!\n");
pthread_join(tid, NULL);
return 0;
}
例子7:
使用多线程编程,使用消息队列通信方式,实现两个进程之间的双向通信。
进程A
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#define MSQ_PATH "."
#define KEY_ID 12
#define MTYPE_1 1
#define MTYPE_2 2
#define BUF_SIZE 1024
int msqid; //消息队列操作ID
struct msgbuf{
long mtype; //消息类型
char mtext[BUF_SIZE]; //消息数据
};
//子进程循环从消息队列中获取类型1的消息
void *routine(void *arg)
{
struct msgbuf msgp2;
for( ; ; )
{
memset(msgp2.mtext,0,sizeof(msgp2.mtext)); //清空缓存
msgrcv(msqid,&msgp2,sizeof(msgp2.mtext),MTYPE_2,0); //接收消息
printf("recv from rose : %s",msgp2.mtext); //打印接收的数据
}
}
int main(int argc,char *argv[])
{
struct msgbuf msgp1;
//1,获取消息队列key值
key_t key = ftok(MSQ_PATH,KEY_ID);
if(-1 == key)
{
perror("ftok failed");
return -1;
}
//2,获取消息队列操作ID
msqid = msgget(key,IPC_CREAT | 0666);
if(-1 == msqid)
{
perror("msgget failed");
return -1;
}
//3,创建线程
pthread_t tid;
pthread_create(&tid,NULL,routine,NULL);
//4,父线程发送
for( ; ; )
{
memset(msgp1.mtext,0,sizeof(msgp1.mtext));
msgp1.mtype = MTYPE_1;
fgets(msgp1.mtext,sizeof(msgp1.mtext),stdin);
msgsnd(msqid,&msgp1,sizeof(msgp1.mtext),0);
}
return 0;
}
进程B
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#define MSQ_PATH "."
#define PROJ_ID 12
#define MTYPE_1 1 //消息类型1
#define MTYPE_2 2 //消息类型2
#define BUF_SIZE 1024
int msqid; //消息队列操作ID
struct msgbuf {
long mtype; //消息类型
char mtext[BUF_SIZE]; //消息数据
};
//子进程循环从消息队列中获取类型1的消息
void *routine(void *arg) //子线程
{
struct msgbuf msgp2;
for(; ;) //死循环
{
memset(msgp2.mtext, 0, sizeof(msgp2.mtext)); //清空缓存
msgrcv(msqid, &msgp2, sizeof(msgp2.mtext), MTYPE_1, 0); //接收消息
printf("recv from jack : %s", msgp2.mtext); //打印接收的数据
}
}
/*进程B*/
int main(int argc, char *argv[])
{
struct msgbuf msgp1; //消息缓冲区
//1, 获取消息队列key值
key_t key = ftok(MSQ_PATH, PROJ_ID);
if(-1 == key)
{
perror("ftok failed");
return -1;
}
//2, 获取消息队列操作ID
msqid = msgget(key, IPC_CREAT | 0666);
if(-1 == msqid)
{
perror("msgget failed");
return -1;
}
//3, 创建线程
pthread_t tid;
pthread_create(&tid,NULL,routine,NULL);
//父进程循环往消息队列发送类型为2的消息
for( ; ;)
{
memset(msgp1.mtext, 0, sizeof(msgp1.mtext));
msgp1.mtype = MTYPE_2;
fgets(msgp1.mtext, sizeof(msgp1.mtext), stdin); //scanf
msgsnd(msqid, &msgp1, sizeof(msgp1.mtext), 0); //发送消息
}
return 0;
}
例子8:
设计程序,实现开发板一边循环显示图片,一边获取触摸屏坐标。(例如:循环显示5张图片,间隔时间为1s, 点击屏幕暂停显示,再次点击继续显示)
提示:一条线程循环播放图片,另一条线程获取触摸屏点击信息。如果获取到点击坐标,就让另一条线程停下来。怎么停下来? 设置标志位:全局变量 int dispaly_flag = 0;
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h> //输入子系统的头文件
#include <sys/mman.h>
#define LCD_PATH "/dev/fb0"
#define TS_PATH "/dev/input/event0"
int ts_x, ts_y; //存放点击屏幕的横纵坐标
int lcd_fd;
int *FB;
int dispaly_flag = 0;
int tp_time = 0;
//1,LCD初始化函数
void Lcd_Init(void)
{
//1,打开LCD文件
lcd_fd = open(LCD_PATH, O_RDWR);
if(-1 == lcd_fd)
{
perror("open lcd failed");
return;
}
//2,lcd映射到用户空间
FB = mmap(NULL, 800*480*4, PROT_READ | PROT_WRITE, MAP_SHARED, lcd_fd, 0);
if(MAP_FAILED == FB)
{
perror("mmap lcd failed");
return;
}
}
//2,LCD释放函数
void Lcd_Uninit(void)
{
//4,解除映射,关闭文件
munmap(FB, 800*480*4);
close(lcd_fd);
}
//3,显示宽度为win,高度为high的bmp图片 在起点坐标为(x_s, y_s)这个点开始显示图片
void Show_Bmp(int win, int high, int x_s, int y_s, char *picname)
{
int i, j;
int tmp;
char buf[win*high*3]; //存放图片的原始数据
int bmp_buf[win*high]; //存放转码之后的ARGB数据
//1,lcd初始化
Lcd_Init();
//2,读取图片数据,并且进行转码 RGB -> ARGB
//打开图片
FILE *fp = fopen(picname, "r");
if(NULL == fp)
{
perror("fopen failed");
return;
}
//读取图片像素点原始数据
fseek(fp, 54, SEEK_SET);
fread(buf, 3, win*high, fp);
//将读取的数据进行转码
for(i=0; i<win*high; i++)
{
//ARGB R G B
bmp_buf[i] = buf[3*i+2]<<16 | buf[3*i+1]<<8 | buf[3*i];
}
//将转码的数据进行倒转 把第i行,第j列的点跟第479-i行,第j列的点进行交换
for(i=0; i<high/2; i++) //0~239行
{
for(j=0; j<win; j++) //0~799列
{
//第i行,第j列的点跟第479-i行,第j列的点进行交换
tmp = bmp_buf[win*i+j];
bmp_buf[win*i+j] = bmp_buf[win*(high-1-i)+j];
bmp_buf[win*(high-1-i)+j] = tmp;
}
}
//3,将转码之后的数据写入LCD (写入到LCD的区域由 (0,0) --> (100, 20))
for(i=y_s; i<high+y_s && i<480; i++) // 0 ~ high-1行 20 ~ high+20-1
{
for(j=x_s; j<win+x_s && j<800; j++) // 0~win-1列 100 ~ win+100-1
{
//FB[800*i+j] = bmp_buf[win*i+j];(图片的数组中第i行,第j列的点)
FB[800*i+j] = bmp_buf[win*(i-y_s)+j-x_s];
}
}
//4,lcd资源销毁,关闭图片
fclose(fp);
Lcd_Uninit();
}
int get_ts(void)
{
//1,打开触摸屏文件
int fd = open(TS_PATH, O_RDWR);
if(-1 == fd)
{
perror("open ts failed");
return -1;
}
//2,读取触摸屏文件数据
struct input_event xy;
int flag = 0; //记录当前获取坐标的信息
while(1)
{
read(fd, &xy, sizeof(xy));
//printf("type:%d, code:%d, value:%d\n", xy.type, xy.code, xy.value);
if(xy.type == EV_ABS && xy.code == ABS_X && flag == 0)
{
ts_x = xy.value * 800 / 1024; //获取点击的时候X轴坐标的值 (0~1024)--> (0~800)
flag = 1;
}
if(xy.type == EV_ABS && xy.code == ABS_Y && flag == 1)
{
ts_y = xy.value * 480 / 600; //获取点击的时候Y轴坐标的值 (0~600)-->(0~480)
flag = 2;
}
//设置条件:每读取一次完整的坐标,就打印一次坐标
if(flag == 2)
{
flag = 0;
printf("(%d,%d)\n", ts_x, ts_y);
break; //获取一次坐标就跳出循环
}
}
//3,关闭触摸屏文件
close(fd);
}
void *routine(void *srg)
{
for(; ;)//死循环
{
if(0 == dispaly_flag) //全局变量dispaly_flag判断主线程是否触摸了屏幕
{
tp_time++; //图片++
}
if(tp_time>5) {tp_time=1;}
switch(tp_time) //图片显示函数
{
case 1:Show_Bmp(800, 480, 0, 0, "bmp1.bmp");break;
case 2:Show_Bmp(800, 480, 0, 0, "bmp2.bmp");break;
case 3:Show_Bmp(800, 480, 0, 0, "bmp3.bmp");break;
case 4:Show_Bmp(800, 480, 0, 0, "bmp4.bmp");break;
case 5:Show_Bmp(800, 480, 0, 0, "bmp5.bmp");break;
}
sleep(1);
}
}
int main(int argc,char *argv[])
{
int x, y;
//1,创建子线程
pthread_t tid;
pthread_create(&tid,NULL,routine,NULL);
for(; ;)
{
get_ts(); //屏幕触摸函数
if(ts_x != 0 && ts_y != 0)
dispaly_flag =! dispaly_flag; // 状态取反
printf("=%d=%d=",ts_x,ts_y);
}
return 0;
}