线程与竞争

线程与竞争

例1

	先看代码:
	mian线程负责创建200个线程,被创建的线程用于文件操作

code1
code2

	请看运行结果

ans1

	可以看到每次运行的适合,运行结果都不一样,这是为什么呢?
	原因在于,我们的操作并不原子,这就会导致竞争现象的发生
	竞争:在没有约定的情况下,大家都抢占这个资源
	结果:结果都不一样
	
	那么有一个问题,如何操作才必定引发竞争,
	其实很简单,就是让某一个线程运行时间尽可能变长,也就是sleep,所以我们的线程程序的运行时间应该尽可能的短
	
	那么回过头来,为什么我们上诉程序会出现竞争?
	看我们线程的参数p,他们都指向了同一个i的不同值,那么很有可能,线程里的i还没有获取到mian线程(注意不是主线程,线程之间并没有主次关系)里的i值
	这点是我们无法保证的 
	所以最根本的原因在于,我们使用了地址传参,我们传递了main线程里的i的地址,创建的线程里都访问同一片地址
	
	
	所以解决办法是,直接传递强转以后的i,转成void*类型
	那么这个时候还有一个问题就是类型转换引发的警告,当然了,这个是确确实实可以忽略的警告,作为c/c++程序员的我们不应该任何一种警告,除了这个
	那么附上更改后的部分和运行结果(顺序不对是因为有并发)
	最终有30000000-30000200有18个质数(7个0)

code3

code4
ans2
ans3

	其实可以发现,强转是一个不算办法的办法,那么继续思考 如何不使用强转来实现这个功能呢?
	再次思考为什么会引发竞争?
	每个新创建的线程的指针都会指向同一片地址
	所以问题就很简单啊,每个指针都指向不同的空间就可以了,数组,结构体,甚至临时变量都可以
	
	这里以结构体为例(加大难度和工程量,毕竟是为了学习)
	
	附上代码(结果不变就不发了)

code5
code6

	这里大家会注意到,我的free和malloc是分离的,其实有句经验之谈,尽量在同一个函数中,如果不能在同一个函数,那也尽量在同一个模块中
	
	那么我们就把传来的指针返回,然后在mian线程里用一个指针来接收,然后在join后释放即可
	
	更新后的代码如下:
	(有人又可能说啦,你这样是一个指针指向好多地址,会不会又有竞争啊?拜托,抢占资源是多个人抢少的资源,一个人怎么抢占资源啊)

code7

code8

	其实还有一个问题,我们的进程创建多少个线程?
	
	本人操作系统并没有了解64位,在32位下,每个进程都有4G的虚拟空间,要预留一些给内核,栈,堆,静态全局,然后我们知道,线程的代码段是共享的,栈却是独立的
	使用ulimit -a可以看到栈至少的大小,假设为10mb
	那么每次创建一个线程都要有10mb的栈,所以进程能创建多少线程取决于栈这些的大小(32位大约是300个左右)
	64位下,每个进程有128p,即使一个栈1G,也用不完,那么先消耗完的不是内存空间,而是线程标识,所以限制的不是资源而是线程标识数量
	
	所以上面程序的宏定义是有问题的(线程数量那个宏),范围应该在300以内

例2

	请看代码
	mian线程创建多个线程,线程里打开文件,读文件,并让文件里的值+1后重新写入文件(即可读完修正文件指针指向)

code10

code11

	在out文件里,写入一个1,如果成功执行,那么out就会变成21,41, 61····
	但是请看结果,所以很明显,有的线程偷懒了(bushi)

ans4

	那么如何解决问题呢?
	问题的本质其实就是多个人使用同一个东西,怎么样?答案是不是呼之欲出,没错,熟悉进程的同学应该想到了, 就是信号量的原理
	互斥量:
		pthread_mutex_init()(动态初始化)
		参数:第一个参数:pthread_mutex_t类型的变量的地址, 第二个参数, 也是pthread_mutex_t类型的变量的地址,告诉程序要初始化成什么样子
		功能:
		返回值:int
		
		pthread_mutex_destroy()
		
		pthread_mutex_lock()
		参数:pthread_mutex_t类型的地址
		功能:上锁,限制某段代码执行
		返回值:int
		(阻塞)
		
		pthread_mutex_unlock()
		参数:pthread_mutex_t类型的地址
		功能:解锁
		返回值:int
		
		pthread_mutex_trylock()
		参数:pthread_mutex_t类型的地址
		功能:上锁
		返回值:int
		(非阻塞)
		
		静态初始化:pthread_mutex_t mutex = PHHREAD_MUTEXINITIALIZER;(用宏)
		
		什么时候静态什么时候动态呢?
		如果是预先定义出来变量的话,显然是静态更好,属性也是默认值,如果你希望更改属性,或者这个互斥量位于结构体中,那么就需要使用动态初始化
		
		那么回到刚刚那个例子,20个线程同时打开文件行不行?行
		20个线程同时读文件行不行?其实也可以,前提是20个线程只是读,如果只是读就无所谓多少个线程了
		到这里大家应该就可以想到,当有一个线程在读的时候,会不会有其他的线程在执行写?有这个可能吧,对吧
		所以我们上面那个程序只能一个一个读,一个一个写
		大家可以注意到,上面的程序是fprintf也就是全缓冲模式(全缓冲模式:当缓冲区被全部填满的时候才会真正执行操作),所以fclose也只能一个一个关闭,除非在fclose和fprintf之间刷新缓冲区
		也就是fflush
		
		这里我们把只能一个一个做的叫临界区,我们需要做的就是在临界区前lock住,然后临界区后unlock,保证每个线程都是独占的
		
		这里提一下,我们想做的是限制文件是互斥的,在互斥量中没办法保证互斥的是什么,文件,资源,所以互斥量只是限制代码
		
		然后,请看代码和结果
		注意我的写法:静态初始化+上锁+解锁+销毁

code12

code13

code00

ans5

	可以看到这里已经没有竞争了,即使加了sleep也没有问题,下面分析一下程序
	
	程序中thr_add函数被扔出去20次,也就是20个线程在跑,这20份线程可以同时打开文件,但是因为mut变量是静态全局变量,所以20个线程
	只能有一个线程会真正在执行,其他的线程怎么办?都阻塞在lock函数那里,只有那个抢到的线程解锁以后,其他线程才有机会继续抢占资源
	```
	OK,下面看另一个例子
	有四个线程,都往终端上输出A, B, C, D
	

例3

code14

code15

ans6

	程序里,还是和之前例子差不多,mian线程负责创建4个线程,线程负责往1里打印字符,这里解释一下1是什么东西,请看图

cnt1

	可以看到,由于没有互斥量,输出都是随机的,那有个问题, 如何工工整整的输出A, B, C, D?
	先上代码

code16

code17

ans7

	讲解一下,我们在全局区创建静态互斥量,然后每初始化一个就锁住一个,这里假设线程编号为A, B, C, D
	A线程在开始前就已经被锁住了,所以在线程运行的时候,会阻塞在27行的lock,其余线程同理,直到什么时候呢?
	在程序的53行,我们人为的把A线程解锁,那么A线程就开始运行,它把自己锁住,然后输出A, 最后把B线程解锁,然后B线程就开始运行,先把自己锁住,并输出一个B,然后解锁C
	然后其余线程往复如此, 就像一个锁链一般
	这里需要注意的是,锁链是未定义行为,这里使用这个例子只是为了学习关于互斥量的使用,其实其他的办法有很多
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值