线程的概念和创建

线程

概念:线程是进程内部的一条执行序列(执行流),一个进程可以包含多个线程。将main函数执行的线程称之为主线程,其他线程的线程称之为函数函数线程。

C语言如何组织一组指令—>函数

main函数:进程执行的入口。

创建线程时,需要指定线程的执行序列(函数)。

函数调用和线程函数

函数调用

1
2
3
4
int main()	//串行执行
{
fun(); //函数调用栈
}

线程函数

1
2
3
4
5
void *fun(void *arg);
int mian() //并发执行
{
pthread_create(fun);//只是给一个函数地址,来指定创建的线程从哪个函数开始执行
}

线程的实现方式

  • 用户级线程:n:1
  • 内核级线程:线程的实现是在内核态 n:n
  • 混合级线程: n:m

内核态线程

优点:用户程序比较简单,一个线程阻塞,可以很从容的切换到另一个线程。

缺点:切换效率低,每次切换都需要陷入内核态,占用内核稀缺的内存资源。

用户级线程

优点:灵活,内核不需要知道线程的存在,切换效率高,因为不需要陷入内核,不用修改操作系统,实现简单。

缺点:如果一个线程阻塞,则会造成整个线程的阻塞,用户程序就会相对复杂一些。

Linux系统的线程实现:

Linux实现线程的机制非常独特,从内核角度来说,他并没有线程这个概念。线程仅仅被视为一个与其他进程共享某些资源的进程。

进程和线程的区别

  • 进程是资源分配的最小单位,线程是CPU调度的最小单位
  • 进程有自己的独立地址空间,线程共享进程中的地址空间
  • 进程的创建消耗资源大,线程的创建相对较小
  • 进程的切换开销大,线程的切换开销小

Linux系统上线程库的使用

1
2
3
4
5
6
7
8
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,失败返回错误码
int pthread_exit(void *result);//结束一个线程并且设置线程结束的一些信息,结束进程但是不想结束线程
int pthread_join(pthread_t id,void **result);//等待一个ID指定的线程结束,result获取ID线程结束时设置的结束信息。

使用这个方法来测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#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);
}
}

编译的时候要连接pthread库

给线程函数传递参数

1
void *  传递一个小于等于4字节的值类型,传递一个地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>

#include<pthread.h>

void *fun(void *);

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)arg;
printf("b=%d\n",b);
int i=0;
for(;i<3;i++)
{
printf("fun running\n");
sleep(1);
}
}

1
传递一个地址//函数线程和传递参数的线程共享传递的变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>

#include<pthread.h>

void *fun(void *);

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*)arg;
printf("b=%d\n",b);
int i=0;
for(;i<3;i++)
{
printf("fun running\n");
sleep(1);
}
}

线程之间除了地址空间共享,还有那些是共享的:

进程:.text .data .heap .stack 内核(文件描述符)

结论:同一个进程中的线程共享.data .heap .text 文件描述符(无论什么时候打开)