Linux — 浅析线程以及多线程的同步与互斥

                   浅析多线程以及多线程的同步与互斥





线程概念
                                                                                                      


什么是线程?

线程是进程内部的一个执行分支,线程的创建成本小于进程.Linux下没有真正意义上的线程,只有我们用进程模拟出来的线程,虽然是假的但是效率很高. 因为有PCB高度一致,复用性高,系统维护也很容易.


线程和进程的区别:

1.我们都知道每一个进程的开辟成本是一个大概4GB的虚拟内存,建立众多的数据表来维护其代码段、堆栈段和数据段,这种多任 务工作方式的代价非常“昂贵”,相比较进程来讲,线程的开辟成本就节约许多, 运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且线程间彼此切换所需要时间也远远小于进程间切换所需要的时间.

2.线程之间通信是一个 线程间方便的通信机制。对不同进程来说它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行。这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其他线程所用,不仅方便,而且快捷.




线程需要用到的函数
                                                                                                      


创建一个线程:

创建线程实际上就是确定调用该线程函数的入口点,这里通常使用的函数是pthread_create()。在线程创建后,就开始运行相关的线程函数。

函数原型:
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void), void *restrict arg);

参数类型:tidp:要创建的线程的线程id指针
 	 attr:创建线程时的线程属性
	 start_rtn:返回值是void类型的指针函数
 	 arg:start_rtn的形参

返回值:若是成功建立线程返回0,否则返回错误的编号


等待线程结束函数:


由于一个进程中的多个线程是共享数据段的,因此,通常在线程退出后,退出线程所占用的资源并不会随着线程的终止而得到释放。正如进程之间可以用wait()系统调用来同步终止并释放资源一样,线程之间也有类似机制,那就是pthread_join()函数。pthread_join()用于将当前进程挂起来等待线程的结束。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源就被收回。

函数原型:int pthread_join(pthread_t thread, void **retval);
 
  
参数类型:thread :被等待的线程标识符
	retval :一个用户定义的指针,它可以用来存储被等待线程的返回值

返回值:若是成功建立线程返回0,否则返回错误的编号

 
  

线程终止函数:


在线程创建后,就开始运行相关的线程函数,在该函数运行完之后,该线程也就退出了,这也是线程退出的一种方法。另一种退出线程的方法是使用函数pthread_exit(),这是线程的主动行为。这里要注意的是,在使用线程函数时,不能随意使用exit()退出函数来进行出错处理。由于exit()的作用是使调用进程终止,而一个进程往往包含多个线程,因此,在使用exit()之后,该进程中的所有线程都终止了。在线程中就可以使用pthread_exit()来代替进程中的exit()。

函数原型:void pthread_exit(void* retval);

参数类型  :retval:函数的返回指针,只要pthread_join中的第二个参数retval不是NULL,这个值将被传递给retval

返回值:无

线程取消函数
前面已经提到线程调用pthread_exit()函数主动终止自身线程,但是在很多线程应用中,经常会遇到在别的线程中要终止另一个线程的问题,此时调用pthread_cancel()函数来实现这种功能,但在被取消的线程的内部需要调用pthread_setcancel()函数和pthread_setcanceltype()函数设置自己的取消状态。例如,被取消的线程接收到另一个线程的取消请求之后,是接受函数忽略这个请求;如果是接受,则再判断立刻采取终止操作还是等待某个函数的调用等。
函数原型:int pthread_cancel(pthread_t thread); 参数类型:thread:要取消线程的标识符ID 返回值:若成功返回0,若失败则返回失败编号
获取当前线程标识ID
函数原型:pthread_t pthread_self(void); 参数类型:无 返回值:当前线程的线程ID标识
分离释放线程
函数原型:int pthread_detach(pthread_t thread); 参数类型:thread:要释放线程的标识符ID 说明:linux线程执行和windows不同,pthread有两种状态joinable状态和unjoinable状态。一个线程默认的状态是joinable,如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符(总计8K多)。只有当你调用pthread_join之后这些资源才会被释放。若是unjoinable状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放。unjoinable属性可以在pthread_create时指定,或在线程创建后在线程中pthread_detach自己, 如:pthread_detach(pthread_self()),将状态改为unjoinable状态,确保资源的释放。如果线程状态为joinable,需要在之后适时调用pthread_join。 返回值:若成功贼返回0,若失败贼返回错误编码. 




线程间的同步与互斥
                                                                                                      


同步与互斥的概念:

当线程并发执行时,由于资源共享和线程协作,使用线程之间会存在以下两种制约关系。

1.间接相互制约。一个系统中的多个线程必然要共享某种系统资源,如共享CPU,共享I/O设备,所谓间接相互制约即源于这种资源共享,打印机就
是最好的例子,线程A在使用打印机时,其它线程都要等待。

2.直接相互制约。这种制约主要是因为线程之间的合作,如有线程A将计算结果提供给线程B作进一步处理,那么线程B在线程A将数据送达之前都将处于阻塞状态。


间接相互制约可以称为互斥,直接相互制约可以称为同步,对于互斥可以这样理解,线程A和线程B互斥访问某个资源则它们之间就会产个顺序问题——要么线程A等待线程B操作完毕,要么线程B等待线程操作完毕,这其实就是线程的同步了。因此同步包括互斥,互斥其实是一种特殊的同步。


由于线程共享进程的资源和地址空间,所以在访问到他们的公共资源的时候,一定会出现线程的同步和互斥现象,多线程访问一个数据的次序一定是杂乱无章的,所以这也是我们一个非常头疼的一个问题,这时候我们就可以灵活运用到互斥锁和信号量,对一份资源让线程一个一个的访问,让程序的可控性提升.信号量我们以前提到过,所以今天着重来看这个互斥锁.


互斥锁:用于保证在某一段时间只有一个线程在执行某些代码.

互斥锁最简单的使用是这样的:

pthread_mutex_t mutex;                   定义锁
pthread_mutex_init(&mutex, NULL);   默认属性初始化锁
pthread_mutex_lock(&mutex);           申请锁
pthread_mutex_unlock(&mutex);       释放锁

以上函数也是通俗易懂的,我们熟练运用即可掌握.接下来我们通过一个例子来感受一下,线程的同步和互斥.





我们先观察这个代码,我们定义了一个count全局变量,然后用两个线程同时访问这份资源,每个线程访问5000次,每次将count
加上1,那么预期结果当最后输出时count的值为10000,那么现在我们来执行该段代码.

第一次执行:

第二次执行:


第三次执行:
 

我敢保证这三次我运行的是同一个代码,当你多次执行也会出现下面的问题,但是我们预期的是10000啊,为什么会出现这样的情况? 这里就是线程的同步与互斥,当线程访问同一片内存时,现在我先假设一个极端的问题,因为两个线程各自都有一份栈帧, 他们同时拿到了tmp = 0的时候,然后在自己的栈帧中完成对count的赋值,比如线程1刚刚对count赋值为1后,线程2再次对count赋值为1,所以看起来是两次操作,但实际count只被加了一次,但是这里过程会有5000次,里面发生这样的情况概率还是很高的,所以他的值远小于10000,现在我们尝试解决它,我们开始使用互斥锁,让每个线程在某段时间唯一的执行某段代码.


加锁后的代码:




第一次运行结果:


第二次运行结果:


第三次运行结果:



我敢保证。。。。我真的运行了三次,我们发现加锁后的结果是我们所预期的结果,这就是互斥锁的一个最基本而运用,当然 当我们慢慢地实践会有更多的地方用到这些知识,我们只需要根据经验灵活运用即可. 虽然没多少内容这个也是我对线程这个知识点的一点理解和运用,这里的知识其实经过几次实践,记住函数的使用方法和基本操作指令,那么掌握这个知识点也是没有问题的.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值