【Linux】线程的概念与线程库的创建

一、线程的概念
1、线程是进程内部的一条执行序列(执行流),一个进程可以包含多个线程。将main函数执行的线程称之为主线程,其他线程的线程称之为函数线程。
在这里插入图片描述

一个线程,就是执行流一组有序指令。
2、函数调用和线程函数的区别
(1)函数调用
比如一段代码:

int main()
{
	fun();
}

它的函数调用栈大概流程是这样的:
在这里插入图片描述
当把fun函数里面的指令执行完成以后退回到main中继续执行,这就是函数调用的简单过程。详细过程参考博客:

(2)线程函数
先看一段简单的代码

void* fun(void* arg);
int main()
{
	pthread_create(fun);
}

这块的fun只是给定一个函数的地址,来指定创建的线程从哪个函数开始执行。
二、线程的实现方式
线程的实现方式一共有三种:用户级线程、内核级线程、混合级线程。如下图所示:
在这里插入图片描述
1、概念
(1)用户级线程:线程的实现和管理都是在用户空间完成的,所以这块有个线程库存在。
(2)内核级线程:线程的实现是在内核态。
(3)混合级线程:内核态和用户态都是多线程。提供的线程库能让用户在用户状态去创建线程。
2、优缺点
(1)内核态线程的优点

  • 用户程序比较简单
  • 一个线程阻塞,可以很从容的切换到另一个线程
    (2)内核态线程的缺点
  • 切换效率较低,每次切换都需要陷入内核态
  • 占用内核稀缺的内存资源
    (3)用户级线程的优点
  • 灵活,内核不需要知道线程的存在
  • 切换效率高,因为不需要陷入内核
  • 不用修改操作系统,实现简单
    (4)用户级线程的缺点
  • 如果一个线程阻塞,则会造成整个进程的阻塞
  • 用户程序就会相对复杂一些
    (5)混合级线程兼有用户级线程和内核级线程的特点,一个线程阻塞时,会切换到其他线程上。
    三、Linux线程的实现
    Linux实现线程的机制非常独特。从内核的角度来说,它并没有线程这个概念。Linux 把所有的线程都当做进程来实现。内核并没有准备特别的调度算法或是定 义特别的数据结构来表征线程。相反,线程仅仅被视为-一个与其他进程共享某些资源的进程,每个线程都拥有唯一隶属于自己的task_ struct, 所以在内核中,它看起来就像是一一个普通的进程(只是线程和其他- -些进程共享某些资源,如地址空间)。
    四、进程与线程的区别
  • 进程是资源分配的最小单位,线程是CPU调度的最小单位。CPU执行的最小单位是指令,而线程是由指令构成的。
  • 进程有自己的独立地址空间,线程共享进程中的地址空间
  • 进程的创建消耗资源大,线程的创建相对较小。
  • 进程的切换开销大,线程的切换开销相对较小(同一个进程中的线程切换)。
  • 一个线程只能属于一个进程,但是一个进程可以拥有多个线程。
  • 父子进程使用进程间通信机制,同一进程通过读取和写入数据到进程变量来通信
  • 子进程不对任何其他子进程施加控制,进程的线程可以对同一进程的其它线程施加控制。子进程不能对父进程施加控制,进程中所有线程都可以对主线程施加控制。
    五、Linux上线程库的使用
    1、常用方法
    int pthread_create(pthread_t * id, pthread_attr_t * attr, void * ( * pthread_fun)(void * ),void * arg)

id:传递pthread_t类型变量的地址,用于返回创建的线程的ID
attr:设置线程的属性,默认属性直接传递NULL
pthread_fun:线程函数的地址(用户实现的一个函数)
arg:传递给线程函数的参数
成功返回0,失败返回错误码

系统上实现:
代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <pthread.h>
void *fun(void*);
int main()
{
	pthread_t id;
//fun并不是函数调用,仅仅是给了一个函数的地址
	int res = pthread_create(&id, NULL,fun,NULL);
	assert(res == 0);
	int i = 0;
	for( ; i<5; ++i)
	{
		printf("main running\n");
		sleep(1);
	}
	exit(0);	
}
void *fun(void *arg)
{
	int i = 0;
	for(; i<3; ++i)
	{
		printf("fun running\n");
	 	sleep(1);
	}
}

在这里插入图片描述
并发执行的过程。
2、其他方法
(1)int pthread_exit(void * result);

pthread_exit():结束一个线程并且设置线程结束的一些信息

在线程结束时不用调用exit(0),直接调用pthread_exit(NULL)即可
(2)int pthread_join(pthread_t id, void ** result);

pthread_join():等待一个ID指定的线程结束
result:获取ID线程,结束时设置的结束信息

六、给线程函数传递参数
传参的类型为void*,所以有两种方法
1、传递一个小于等于4字节的值类型
比如我们将上述代码改为这样:

int main()
{
	int a = 10;
	pthread_t id;
//fun并不是函数调用,仅仅是给了一个函数的地址
	int res = pthread_create(&id, NULL,fun,(void*)a);
	assert(res == 0);
	int i = 0;
	for( ; i<5; ++i)
	{
		printf("main running\n");
		sleep(1);
	}
	exit(0);	
}
void *fun(void *arg)
{
	int b = (int)agr;
	printf("b == %d\n",b);
	int i = 0;
	for(; i<3; ++i)
	{
		printf("fun running\n");
	 	sleep(1);
	}
}

在这里插入图片描述
我们可以看到b的值被传递成了10.
2、传递一个地址
可以将上述代码改为:

int main()
{
	int a = 10;
	pthread_t id;
//fun并不是函数调用,仅仅是给了一个函数的地址
	int res = pthread_create(&id, NULL,fun,(void*)&a);
	assert(res == 0);
	a = 20;
	int i = 0;
	for( ; i<5; ++i)
	{
		printf("main running\n");
		sleep(1);
	}
	exit(0);	
}
void *fun(void *arg)
{
	sleep(1);
	int b = *(int*)agr;
	printf("b == %d\n",b);
	int i = 0;
	for(; i<3; ++i)
	{
		printf("fun running\n");
	 	sleep(1);
	}
}

在这里插入图片描述
b的值变为了20.是因为我们将a的地址传递给了fun方法,然后将a的值赋值成20的时候,再通过地址值搜索a的值的时候a已经变成了20.
七、结论:同一个进程中线程共享.data、.head、.text和文件描述符(无论什么时候打开)

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值