【Linux】线程(四)

线程(四)

前言:前面主要学习了线程控制、线程安全,互斥锁和消息队列的使用、生产者与消费者模型实现。

信号量

作用:用于实现线程间的同步与互斥
本质:是一个计数器+等待队列,并且向用户提供的使线程等待与唤醒的功能接口
原理:通过计数器对临界资源进行计数,当用户访问临界资源时先访问信号量,通过计数判断是否能够访问,若计数<=0则表示不能访问,则是线程等待并且计数-1;其他线程产生资源后,计数+1,并且唤醒等待;
同步实现:计数的条件判断+等待与唤醒接口
互斥实现:0/1的计数器,进行状态标记
POSIX信号量
POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用
于线程间同步。
初始化信号量

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
pshared:0表示线程间共享,非零表示进程间共享
value:信号量的初始值

销毁信号量:

int sem_destroy(sem_t *sem);

等待信号量:

功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem);

发布信号量

功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);

其他常见的锁
悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,
行锁等),当其他线程想要访问数据时,被阻塞挂起。
乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据
前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。
CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若
不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。
自旋锁:循环一致判断条件是否满足,并不挂机线程(不会放弃cpu,一直强占cpu资源)
在这里插入图片描述
并发:切换调度运行(轮询处理)
并行:同时运行

线程安全的单例模式

什么是单例模式
单例模式是一种 “经典的, 常用的, 常考的” 设计模式.
什么是设计模式
IT行业这么火, 涌入的人很多. 俗话说林子大了啥鸟都有. 大佬和菜鸡们两极分化的越来越严重. 为了让菜鸡们不太拖大佬的后腿, 于是大佬们针对一些经典的常见的场景, 给定了一些对应的解决方案, 这个就是设计模式
单例模式的特点
某些类, 只应该具有一个对象(一个对象/资源只能被加载一次), 就称之为单例.
例如一个男人只能有一个媳妇.
在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这些数据.
线程安全的单例模式的实现
饿汉实现方式和懒汉实现方式

[洗完的例子]
吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭.
吃完饭, 先把碗放下,然后下一顿饭用到这个碗的时候再洗,这就是懒汉模式

饿汉模式:所有的资源加载/对象实例化都放在程序的初始化阶段,接下来在各个线程中直接使用即可。(多用于后台运行)
优点:使用方便,资源直接加载,因此程序运行流畅,xingenn
缺点:将当前不用的资源加载到内存,资源消耗较大,程序初始化时间较长
举例:
使用静态成员static实现,程序运行初始化阶段完成

#include<iostream>                                                        
#include<stdio.h>
using namespace std;
class Test
{
     private:
       static int data;
     public:
       int *get_instance(){return &data;}
};
int Test::data = 10;
int main()
{
   Test a,b;
    printf("%d-%p\n",*a.get_instance(),a.get_instance());
	printf("%d-%p\n",*a.get_instance(),b.get_instance());
    return 0;
}         

运行截图:
在这里插入图片描述
由上诉截可见,初始化成员变量用的是同一块地址空间,这就是用饿汉方式实现单例模式。
懒汉模式:资源并不在程序初始化阶段全部加载/初始化,而是等到使用的时候才去做判断来加载/初始化资源。
优点:初始化比较快,在运行阶段使用的时候也只需要加载一次(若不释放)。
举例:
同样通过static静态成员变量实现,只不过不定义数据(饿汉模式),而是定义指针,来实现使用时初始化。

#include<iostream>                                                        
#include<stdio.h>
using namespace std;
class Test
{
     private:
       static int *data;
     public:
       int *get_instance()
       {
       		if(data == NULL)
       		{
       			data = new int;
       			*data = 4;
       		}
       		return data;
       }
};
int *Test::data = NULL;
int main()
{
   Test a,b;
    printf("%d-%p\n",*a.get_instance(),a.get_instance());
	printf("%d-%p\n",*a.get_instance(),b.get_instance());
    return 0;
}

运行结果:
在这里插入图片描述
共用了同一对象,懒汉模式实现了单例模式。
优化:
1.volatile关键字防止编译器被过度优化
2.*get_instance函数里操纵了一个全局的临界资源,非线程安全,需要进行加锁
3.防止锁冲突

#include<iostream>                                                        
#include<stdio.h>
#include<mutex> 
using namespace std;
mutex lock;
class Test
{
     private:
       volatile static int *data;
     public:
       int *get_instance()
       {	
       		if(data == NULL){                                                   
        		lock.lock();

       		if(data == NULL)
       		{
       			data = new int;
       			*data = 4;
       		}
       		lock。unlock();
       		}
       		return data;
       }
};
int *Test::data = NULL;
int main()
{
   Test a,b;
    printf("%d-%p\n",*a.get_instance(),a.get_instance());
	printf("%d-%p\n",*a.get_instance(),b.get_instance());
    return 0;
}
发布了29 篇原创文章 · 获赞 66 · 访问量 5817

猜你喜欢

转载自blog.csdn.net/qq_43676757/article/details/105231862