线程&&线程库

一、线程介绍

  •      线程是轻量级的进程;因为它的资源创建轻巧,调度效率快
  •      线程是进程内部的一条执行序列(一组有序指令),或者说是执行流。
  •      一个进程至少有一条线程,即就是main函数所代表的执行序列。称之为主线程,通过线程库可以创建线程----函数线程 
  •      主线程仅仅代表进程执行的第一条线程而已。当主线程通过线程库创建出函数线程以后,所有线程就没有任何区别。
  •      主线程默认结束,结束是整个进程。

二、线程和进程的区别

  1. 进程是资源分配的单位,线程是CPU调度执行的单位;
  2. 多进程:进程间资源都是独立的,同一进程中的多线程,资源是共享的,除了独立的栈区空间
  3. 线程更加轻便,更加小巧,从而造成线程调度,创建、调度、切换效率都比进程高。

三、线程库的使用

#include <pthread.h>

创建:

       int pthread_create(pthread_t  *id,pthread_attr_t  *attr,void * (pthread_fun)(void *), void *arg);

  • id:  线程ID,线程创建时分配的线程ID,传递一个变量的地址
  • attr: 线程属性,默认为NULL;
  • pthread_fun: 线程函数  新创建的线程的执行体;函数地址:指定新线程从那块开始执行。
  • arg: 传递给线程函数的参数。

返回值(res == 0);成功返回0,失败返回错误码


 创建时给函数线程传参的两种方式:

 1、值传递  --最多传递四个字节的数据,---》指针

                      将传递的值直接强转为void*

                      arg: 类型是void*  记录的传递的值

2、地址传递  要进行判断,防止后面的函数线程对其值修改

                    将要传递的值的地址转化为void*

                    arg: 类型是void*   记录的是传递的地址

 

四、线程代码

并行--硬件环境  并发---软件执行

我们要加线程库加上,执行代相关码,gcc -o pthread pthread.c -lpthread

代码如下:

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

#include <pthread.h>


void *fun(void * arg)
{
    int data = * (int*)arg; //地址传递
    //int data = (int)arg;  //值传递
	int i = 0;
	for(; i < 3; ++i)
	{
		sleep(1);
		printf("fun running\n");
	}
}

int main()
{
	pthread_t id;
	int res = pthread_create(&id,NULL,fun,(void*)&data); //不会阻塞
                                           //(void*)data  值传递
	assert(res == 0);

	int i = 0;
	for(; i < 5; ++i)
	{
		sleep(1);
		printf("main running\n");
	}

}

 结果如下:

由此可看,线程的执行顺序并不确定,谁先执行,谁后执行,取决于系统。


我们结合另一个fork函数,使用strace  ./XXX(可执行文件)跟踪查看程序执行中系统的调用函数。来分析下两种的底层调用:

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

int main()
{
	pid_t n = fork();
	assert(n!= -1);
	if(n == 0)
	{
		sleep(1);
	}
	else
	{
		sleep(1);
	}
	
}

结合图:

       linux下调用都是一样的,只是传的参数不一样对应做该做的事情,但都是克隆函数,clone()函数。两个函数都是有自己的pid,和栈空间:fork是全新的函数为0,而线程不会直接分配空间,是共享的空间。


将代码改动后,通过地址传递的方法,来看谁先传递;

代码改动为:

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

#include <pthread.h>

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

int main()
{
	int data = 10;
	pthread_t id;
	int res = pthread_create(&id,NULL,fun,(void*)&data);
	assert(res == 0);

        data = 20;
	int i = 0;
	for(; i < 5; ++i)
	{
		sleep(1);
		printf("main running\n");
	}
       pthread_exit(NULL);
}

运行结果如下:

       凭上图,是否就可以证明先是函数线程执行呢?其实不是,当执行多次后,就不一定是谁先执行了。

       由此可看出来:

       主线程后期对值的修改可能影响函数线程中获取值,函数线程中通过地址对变量修改,也会影响主线程中变量的值。


       main函数结束,进程结束会调用exit(0)函数, 线程依赖着进程,进程结束,所有线程随之结束。

       例如:在函数线程执行的次数大于主线程时,会发现主线程结束后,函数线程是执行不到它相应的次数的。

        接下来我们就需要用线程库中的线程的函数,我给大家介绍几个函数:

        1)int pthread_exit(void* reval); 就可以使进程不结束,将函数线程执行完。

        2)函数等待线程结束,获取线程退出信息:int(pthread_t id,void **retval);  相当于waitpid()这个函数。

        3) 取消一个线程(只是发出取消请求,并不会阻塞):int pthread_cancel(pthread_t id);


五、线程的实现方式

       1.用户级线程---》 内核并不支持多线程,多线程是用户态实现,用户代码就必须实现线程的创建,调度,销毁等工作。

              优点:内核简单,线程切换速度快(每次切换不需要陷入内核)

              缺点:用户代码复杂,如果一条线程阻塞,整个进程都会阻塞。

        2.内核级线程             

              优点:用户简单,线程切换速度慢(每次切换需要陷入内核),一条线程阻塞,可以立刻切换。

              缺点:内核代码复杂。

         3.混合级线程 :一部分实现在内核,一部分实现在用户,结合了两者的优点,但是比较复杂。

         linux中使用的是内核级线程。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值