系统编程第五节——线程

一、线程的概念

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值