目录

一、概念

1.进程与线程的区别是?(常问)

2.线程与fork系统调用

3.线程的优缺点

4.线程的实现方式

二、线程函数

1.pthread_create

2.pthread_exit

3.pthread_join 

4.pthread_cancel

三、线程的使用

1.线程的基本操作

2.并发执行与并行执行


一、概念

  • 线程是程序中完成一个独立任务的完整执行序列,即一个可调度的实体。
  • 根据运行环境和调度者的身份,县城可分为内核线程和用户线程。
  • 线程运行在内核空间,由内核来调度。
  • 当进程的一个内核线程获得CPU的使用权时,他就加载并执行一个用户线程
  • 线程库负责管理所有执行线程,比如线程的优先级、时间片等。线程库利用longjmp来切换线程的执行,使他们看起来像”并发“执行,但实际内核仍然是把整个进程作为最小单位来调度。
  • 一个进程的所有执行线程共享该进程的时间片,他们对外表现出相同优先级。

1.进程与线程的区别是?(常问)

  • 进程:一个正在运行的程序 ,程序执行完,系统进行回收
  • 线程:进程内部的一条执行路径(序列)
  • 不同语言不同平台上线程的实现机制有所不同

2.线程与fork系统调用

  • fork创建出该进程的一份新副本。这个新进程拥有变量自己的PID,它的时间调度也是独立的,他的执行(通常)几乎完全独立于父进程。
  • 进程创建一个新线程时,新的执行线程将拥有自己的栈(因为也有自己的局部变量),但与它的创建者共享全局变量、文件描述符、信号处理函数和当前目录状态。

3.线程的优缺点

优点:

  1. 让程序看起来好像是在同时做两件事是很有用的。多任务工作如果用多进程的方式来完成很难做到高效,因为各个不同的进程必须紧密合作才能满足加锁和数据一致性方面的要求,而用多线程来完成就比多进程容易得多。
  2. 一个混杂着输入、计算和输出的应用程序,可以将这几个部分分离为3个线程来完成,从而优化程序执行的性能。
  3. 一般而言,线程之间的切换需要操作系统做的工作要比进程之间的切换少得多,因此多个线程多资源的需求要远小于多个进程。

缺点:

  1. 编写多线程程序需要非常仔细的设计。在多线程程序中,因时序上细微的偏差或无意造成的变量共享而引发错误的可能性是很大的。
  2. 对多线程程序的调试要比单个线程程序的调试困难得多,因为线程之间的交互难以控制。
  3. 将大量计算分为两个部分,并把这个两个部分作为不同的线程来运行的程序在一台单处理器机器上并不一定运行得更快,除非计算确实允许他的不同部分可以被同时计算,而且运行它的机器拥有多个处理器核来支持真正的多处理。

4.线程的实现方式

分为三种模式:完全在用户空间实现、完全由内核调度和双层调度。

用户级、内核级、组合/混合

用户:用户空间代码管理线程的创建、调度。

        优点:创建开销小,可以创建多个。

        缺点:内核感知不到用户级,内核无法将多个处理器提供给用户使用。即用户级无法使用多个处理器

内核:能够被调度到不同处理器。

组合:介于两者之间。

二、线程函数

头文件:

#include<pthread.h>

1.pthread_create

int pthread_create(pthread_t* thread,const pthread_attr_t* attr,void* (*start_routine)(void*),void* arg);
  • 作用:创建一个线程
  • 参数:
    • 第一个参数thread是新线程的标识符,后续pthread_*函数通过它来引用新进程。其类型的pthread_t定义为:
      • #include<bits/pthreadtypes.h>
        typedef unsigned long pthread_t;
      • 其中可见,pthread_t是一个整型类型。
    • 第二个参数attr用于设置新线程的属性。传递NULL表示使用默认线程属性
    • 第三个参数是返回值、参数变量都为void*的函数指针
    • 第四个参数arg表示新线程的参数
  • 返回值:成功时返回0,失败时返回错误码

2.pthread_exit

        线程一旦被创建好,内核就可以调度内核线程来执行start_rountine函数指针所指向的函数。线程函数在结束时最好调用如下函数:

void pthread_exit(void* retval);

该函数通过retval参数向进程的回收者传递其退出信息。

3.pthread_join 

int pthread_join(pthread_t thread,void**retval);
  • 作用:来回收其他线程(前提是目标线程可回收),即等待其他线程结束,类似于wait的使用
    • 一个进程中的所有线程都可以调用pthread_join函数
    • 该函数会一直被阻塞,直到被回收的线程结束为止。
  • 参数:
    • thread是目标现成的标识符
    • retval是目标线程返回的退出信息
  • 返回值:成功0,失败返回错误码

4.pthread_cancel

int pthread_cannel(pthread_t thread);
  • 作用:终止一个线程,即取消线程
  • 参数:
    • thread是目标线程标识符
  • 返回值:成功0,失败返回错误码

三、线程的使用

1.线程的基本操作

通过下面代码理解,首先主函数内执行一个主线程,fun函数执行一个线程,两线程一起执行:

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

void* fun(void* arg)
{
	for(int i=0;i<5;i++)
	{
		printf("fun run\n");
		sleep(1);
	}
}

int main()
{
	pthread_t id;
	pthread_create(&id,NULL,fun,NULL);
	for(int i=0;i<2;i++)
	{
		printf("main run\n");
		sleep(1);
	}
	exit(0);
}

编译执行时,如果不在执行文件后面加上指定库名''-lpthread'',就会出现如下错误:

 执行结果:

执行结果可以得到一个结论:
主函数先结束,线程也跟着结束(fun只输出了4次)线程先结束,主函数不会结束。所以,为了使主函数”活到“最后,需要添加pthread_join函数阻塞。再添加pthread_exit对main函数返回一个值:

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

void* fun(void* arg)
{
	for(int i=0;i<5;i++)
	{
		printf("fun run\n");
		sleep(1);
	}
	pthread_exit("fun over\n");
}


int main()
{
	pthread_t id;
	pthread_create(&id,NULL,fun,NULL);
	for(int i=0;i<2;i++)
	{
		printf("main run\n");
		sleep(1);
	}
	char* s=NULL;
    pthread_join(id,(void**)&s);
    printf("s=%s",s);
	exit(0);
}

执行结果如下,主函数先结束,然后阻塞住,知道线程也执行结束(输出5次),然后线程结束后向主函数发送一个“fun over”传递其退出信息。

2.并发执行与并行执行

并发与并行:

并发:一个处理器,在一段时间内交替运行

并行:任意时刻,两条程序都在执行(多个处理器)

并行是一种特殊的并发执行。

并行执行

通过创建5个线程,每个线程打印出自己是第几个创建的来观察并行执行的执行结果

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

void* fun(void* arg)
{
	int index=*(int*)arg;
	for(int i=0;i<3;i++)
	{
		printf("index=%d\n",index);
		sleep(1);
	}

}

int main()
{
	pthread_t id[5];
	int i;
	for(i=0;i<5;i++)
	{
		pthread_create(&id[i],NULL,fun,(void*)&i);
	}	
	for(i=0;i<5;i++)
	{
        	pthread_join(id[i],NULL);
	}
	exit(0);
}

每一次的执行结果都会有差异,甚至不是连续01234的数字输出,是因为并发执行是对资源共同操作,会有短暂时间内时序的影响。就比如当一个线程还在执行index++=2时,还是执行出来index=2的结果,另一个线程对index=1的值进行执行。

例题理解并发与并行差异:

通过创建5个线程,每个线程循环1000次++操作,最后打印出i的值,来观察并发执行的执行结果

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

int m_val=1;
void* fun(void* arg)
{
        for(int i=0;i<1000;i++)
        {
                printf("m_val=%d\n",m_val++);
        }
}

int main()
{
        pthread_t id[5];
        int i;
        for(i=0;i<5;i++)
        {
                pthread_create(&id[i],NULL,fun,(void*)&i);
        }
        for(i=0;i<5;i++)
        {
                pthread_join(id[i],NULL);
        }
        exit(0);
}

在多个处理器中执行并行操作,每次的结果不唯一,不一定累加后的值会是5000,如下图所示: 

 执行结果如下,并发执行的结果每次都一样,最终输出值为5个进程,每次打印1000次++的结果,如下图所示,每次打印的最终值都会是5000

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐