一、线程的概念
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和文件描述符(无论什么时候打开)