多线程(LWP:light weight process)编程

简介

linux中没有线程的概念,线程在linux中叫做轻量级进程(light weight process)
线程是多任务并发执行的另一种解决方案。相比于进程的优势在于,分配的资源少,多个线程共享进程的资源,并且其调度效率要高于进程。线程尤其适用于高并发量(尤其是对于web的访问,对于进程而言只能有个几十几百的),短小的任务的并发运行。
线程实际上是应用层的概念,在linux内核中,所有的调度实体都被称为任务(task),他们之间的区别是:有些任务自己拥有一套完整的资源,而有写任务彼此之间共享一套资源。
线程间共享共享代码区、堆区、数据区。
在这里插入图片描述
如上图所示,左边是一个标准进程,它拥有自己一套完整的资源–包括内存空间、文件、信号挂起队列等等,这些资源全部由PCB(即内核结构体task_struct)统一管理,这整套数据结构以及他们的动态变化就是一个进程。对于右边,可以看到有两个PCB结构体共享了很多资源,而一个PCB对应系统中的一个任务,是系统调度器的调度对象,系统在调度的时候并不关心这些PCB究竟是独立拥有一套资源还是跟别人共享,因此右边的等多个调度实体(线程)的进程就比单个调度实体的进程可以获得更多的CPU资源来管理和操作他们的资源。

应用举例

下载工具:对于一个下载任务可以使用多线程。
服务器:高并发的客户端请求。
对于我们的LCD,我们可以开启一个进程,然后在一个进程内跑多个线程,这样就可以对于不同的地方有不同的响应。

线程的API

安装线程手册

sudo apt-get update
sudo apt-get install manpages-posix-dev
创建线程:

在这里插入图片描述
在这里插入图片描述
线程例程指的是:如果线程创建成功,那么该线程就会立即去执行的函数
POSIX线程库的所有API对返回值的处理原则都是一致的,成功返回0,失败返回错误码errno
如果attr是NULL的话那么就会创建一个标准的线程

获取线程ID:
pthread_t pthread_self(void);
设置或获取线程属性

在这里插入图片描述
一条线程如果是可接合的,意味着这条线程在退出时不会自动释放自身资源,而会成为僵尸线程,同时意味着该线程的退出值可以被其他线程获取,因此,如果不需要某条线程的退出值的话,那么最好将线程设置为分离状态,以保证线程不会成为僵尸线程

线程属性变量的使用步骤是

1,定义线程属性变量,并且使用pthread_attr_init( )初始化。
2,使用 pthread_attr_setXXX( )来设置相关的属性。
3,使用该线程属性变量创建相应的线程。
4,使用 pthread_attr_destroy( )销毁该线程属性变量。

创建一个分离属性的线程

/*************************************************************************
	> File Name: 04_detach.c
	> Author: Xianghao Jia
	> mail: xianghaojia@sina.com
	> Created Time: Mon 18 Nov 2019 06:20:41 AM PST
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <pthread.h>

void *routine(void *arg)
{
	printf("hello\n");
	// 由于已分离,该线程退出后会自动释放资源
	pthread_exit(NULL);
}

int main(int argc, char ** argv)
{
	pthread_t tid;
	// 初始化一个属性变量,并将分离属性加入该变量
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
	// 用该属性变量产生一个新线程
	pthread_create(&tid, &attr, routine, NULL);

	// 主线程暂停,否则return语句会导致整个进程退出
	pause();
	return 0;
}
线程调度

在这里插入图片描述
在这里插入图片描述
如果你想给一个线程设置调度方面的属性时,必须将线程的inheritched设置为PTHREAD_EXPLICIT_SCHED。

设置调度策略的API如下
在这里插入图片描述
1. 当线程的调度策略为SCHED_FIFO时,其静态优先级必须设置为1-99,这意味着一旦这种线程处于就绪态时,它能立即抢占任何静态优先级为0的普通线程,采用SCHED_FIFO调度策略的线程还遵循以下规则:
当他处于就绪态时,就会被放入其所在的优先级队列的队尾位置
当被更高优先级的线程抢占之后,它会被放入其所在优先级队列的队头位置,当所有优先级比他高的线程不再运行后,它就恢复执行
当他调用sched_yield后,它会被放入其所在的优先级队列的队尾的位置
总的来说,一个具有SHCED_FIFO调度策略的线程会一直运行到发送IO请求,或者被更高优先级线程抢占,或者调用sched_yield主动让出CPU
2. 当调度策略为SCHED_RR时,情况和SHCED_FIFO是一样的,区别在于:每一个SCHED_RR策略下的线程都会被分配一个额度的时间片,当时间片耗光时,他会被放入其所在优先级队列的队尾的位置,可以用sched_rr_get_interval来获得时间片的具体数值
3. 当线程的调度策略为SCHED_OTHER时,其静态优先级必须设置为0。该调度策略是LINUX系统调度的默认策略,处于0优先级别的这些线程按照所谓的动态优先级被调度,而动态优先级起始于线程的nice值,且每一个线程已处于就绪态但被调度器无视时,其动态优先级会自动增加一个单位,这样能保证这些线程竞争CPU的公平性。

设置静态和动态优先级

在这里插入图片描述
注意:

  1. 静态优先级是一个定义如下的结构体
struct shced_param
{
	int sched_priority;
};

可见静态优先级就是一个只有一个整型数据的结构体,这个整型数值介于0到99之间,0级线程称为非实时的普通线程,他们之间的调度凭借所谓的动态由县级你来博弈,而1-99级线程被称为实时线程,他们之间的调度凭借他们不同级别的静态由下级和不同的调度策略(当静态优先级一致的时候)来博弈
2. 线程的静态优先级之所以被称为静态,是因为只要你不强行使用相关函数修改它,它不会随着线程的执行而发生改变,静态优先级决定了实时线程的基本调度次序,如果他们的静态优先级一样,那么调度策略再为调度器提供进一步的调度依据
3. 线程的动态优先级是非实时的普通线程独有的概念,之所以被称为动态,是因为它会随着线程的运行,根据线程的表现而发送改变。

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>

int a = 0;
int b = 0;
void * routine(void *arg)
{
	//这里一直将其设置着呢,如果只设置一次,应该会均衡。
	if(a == 0)
	{
		a = 1;
		if(*(char *)arg == 'A')
		{
			nice(10);
		}
	}
	if(b == 0)
	{
		if(*(char *)arg == 'B')
		{
			nice(-10);
		}	
	}
	while(1)
	{
		fprintf(stderr, "%c", *(char *)arg);
	}
}
int main(int argc, char const *argv[])
{
	pthread_attr_t attr1;
	pthread_attr_t attr2;
    //线程属性初始化
	pthread_attr_init(&attr1);
	pthread_attr_init(&attr2);
    //设置继承属性
	pthread_attr_setinheritsched(&attr1, PTHREAD_EXPLICIT_SCHED);
	pthread_attr_setinheritsched(&attr2, PTHREAD_EXPLICIT_SCHED);
    //设置调度策略
	pthread_attr_setschedpolicy(&attr1, SCHED_OTHER);
	pthread_attr_setschedpolicy(&attr2, SCHED_OTHER);

	struct sched_param param1;
	struct sched_param param2;
	param1.sched_priority = 0;
	param2.sched_priority = 0;

	pthread_attr_setschedparam(&attr1, &param1);
	pthread_attr_setschedparam(&attr1, &param2);

	pthread_t tid1, tid2;
	pthread_create(&tid1, &attr1, routine, "A");
	pthread_create(&tid2, &attr2, routine, "B");

	pthread_exit(NULL);
}
警戒区和线程栈

在这里插入图片描述
在这里插入图片描述
警戒区是没有任何访问权限的内存,用来保护相邻两条线程的栈空间不被彼此践踏。
线程栈是非常重要的资源 ,用以存放诸如函数形参、局部变量、线程切换现场寄存器数据等等,一个多线程进程的栈空间,包含了所有线程各自的栈。

线程退出和线程等待

线程和进程类似,在缺省的状态下退出之后,会变成僵尸线程,并且保留退出值。其他线程可以通过相关API接合该线程–使其资源被系统回收,如果愿意的话还可以顺便获取其退出值。
在这里插入图片描述
在这里插入图片描述
注意:

  1. 如果线程退出时没有退出值,那么retval可以指定为NULL
  2. pthread_join指定的线程如果尚在运行,那么他将会阻塞等待。如果已经终止,那么就直接返回
  3. 目标线程必须可接合的
  4. 如果retval不是空,那么pthread_join拷贝目标线程的退出状态到retval中,如果目标线程是被取消的,那么retval中存放的就是PTHREAD_CANCEL
  5. pthread_tryjoin_np指定的线程如果尚在运行,那么他将会立即出错返回
    例子
/*************************************************************************
	> File Name: 02_join_exit.c
	> Author: Xianghao Jia
	> mail: xianghaojia@sina.com
	> Created Time: Mon 18 Nov 2019 05:18:40 AM PST
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

void *routine(void *arg)
{
	char *s = (char *)arg;
	printf("arg=%s\n", s);
	sleep(1);
	pthread_exit("bye,bye");
}

int main(int argc, char ** argv)
{
	pthread_t tid;
	if(pthread_create(&tid, NULL, routine, (void*)"test string") != 0)
	{
		perror("pthread_create");
		exit(0);
	}

	void *p;
	pthread_join(tid, &p);
	printf("child thread exit : %s\n", (char *)p);
	return 0;
}
取消一个线程:

有时候我们不能等待某个线程“自然死亡”,而需要勒令其马上结束,此时可以给线程发送一个取消请求,让其中断执行而退出。
在这里插入图片描述
而当线程接收到一个取消请求时,他将会如何表现取决于两个东西:一是当前的取消状态,二是当前的取消类型,线程的取消状态很简单–分别是PTHREAD_CANCEL和PTHREAD_CANCEL_DISABLE,前者是缺省的,代表线程可以接受取消请求,后者表示关闭取消请求,不对其响应
而在线程接受取消请求的情况下,如何停下来又取决于两种不同的响应取消请求的策略–延时响应和立即响应,当采取延时策略时,线程并不会立即退出,而是要遇到所谓的“取消点”之后才退出。而“取消点”指的是一系列指定的函数
在这里插入图片描述
注:对于取消点,系统是有明确的规定的
例子:

/************************************************************************************
				文件名:pthread_cancel.c
				功能概述:获取线程退出值
				作者:贾向浩
				日期:2019年2月7日
************************************************************************************/

/************************************************************************************
									头文件包含区
************************************************************************************/
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
/***********************************************************************************
									函数定义区
************************************************************************************/

/**
 * 函数名:pthread_child
 * 参数:无
 * 返回值:字符串指针
 * 功能描述:测试线程返回
 */

void *pthread_child(void *arg)
{
	long long i,j;
	long long  f1 ,f2;
	//延时
	for(i = 0 ; i < 100000 ;i++)

	{
		for(j = 0 ; j < 10000 ; j++)
		{
			f1 = f2;
		}
	}
	int num = 10000;
	while(num--)
	{
		// 退出点
		fprintf(stderr, "%c", 'x');
	}
	char *msg = "abcd";
	pthread_exit(msg);
}
/**
 * 函数名:main
 * 参数:无
 * 返回值:int
 * 功能描述:测试
 */
int main()
{
	pthread_t tid;
	pthread_create(&tid,NULL,pthread_child, NULL);
	pthread_cancel(tid);
    void *rec;
	errno = pthread_join(tid,&rec);  //如果设置为 可 结合 他会阻塞等待任意子线程
	if(errno == 0)
	{
		printf("join ok!\n");
	}
	else
	{
		perror("join failed\n");
	}
	if(rec == PTHREAD_CANCELED)
	{
		printf("main():thread was canceled!\n");
	}
	else
	{
		printf("main():thread wasn't canceled!\n");
	}
	pthread_exit(NULL);
}
善后处理

由于线程任何时刻都有可能持有互斥锁或者信号量类的资源,一旦被取消很有可能导致别的线程出现死锁,因此如果一条线程的确可能被取消,那么在被取消之前必须使用以下的API来为将来可能出现的取消请求注册“处理例程”,让这些例程自动释放持有的资源。
在这里插入图片描述

/*************************************************************************
  > File Name:    05_pthread_join.c
  > Author:       Jia Xiang Hao
  > Description:  
  > Created Time: 2019年04月03日 星期三 01时58分54秒
 ************************************************************************/

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

pthread_mutex_t m;

void handler(void *arg)
{
	pthread_mutex_unlock(&m); // 解锁
}

void *routine(void *arg)
{
	// 加锁前,将handler压入线程取消处理例程的栈中,以防中途取消
	pthread_cleanup_push(handler, NULL);
	pthread_mutex_lock(&m);

	printf("[%u][%s]:abtained the mutex.\n",
			(unsigned)pthread_self(),
			__FUNCTION__);
	// 在此线程睡眠期间如果收到取消请求,handler将被执行
	sleep(10);
	// 解锁后,将handler从栈中弹出,但不执行
	pthread_mutex_unlock(&m);
	pthread_cleanup_pop(0);
	pthread_exit(NULL);
}

int main(int argc,char *argv[])
{
	pthread_mutex_init(&m, NULL);
	pthread_t tid;
    pthread_create(&tid, NULL, routine, NULL);
	// 1秒钟,发送取消请求
	sleep(1);
	pthread_cancel(tid);
	void *ret;
	pthread_mutex_lock(&m);
	printf("[%u][%s]:abtained the mutex.\n",
			(unsigned)pthread_self(),
			__FUNCTION__);
	pthread_mutex_unlock(&m);

    return 0;
}
线程如何通信呢?

就是靠他们共享的空间来进行通信

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值