线程同步学习

1、线程同步的定义

线程同步不是一起、相同,而是协调、协同的意思。
1)按预定的先后次序进行运行,如:您说完,我再说;线程A生成数据后交给线程B处理;
2)公共资源同一时刻只能被一个线程使用;共享数据在同一时刻只能被一个线程修改,以保证数据的完整性。
基于互斥锁、条件变量、信号量、自旋锁、读写锁。

线程安全的3大特性
1、原子性
原子性是指操作是不可分的。其表现在于对于共享变量的某些操作,应该是不可分的,必须连续完成。
2、可见性
可见性是指一个线程对共享变量的修改,另外一个线程能够立刻看到;

线程池

线程不是开的越多越好,过多可能导致系统的性能更低,CPU在每个线程之间轮询执行,线程数一多,切换线程也就是切换上下文的时间就多了,执行效率就可能降低。
所以就引入了线程池来管理线程,CPU把任务交给线程池,然后由线程池将任务分配给空闲的线程;如果无空闲线程,则会等待线程池里的线程执行完当前任务后再分配,执行任务完毕后再将线程交还给线程池

面试题:单例模式+多线程

单例模式:整个系统生命周期内,一个类只能产生一个实例,相同的内存地址。
好处:节省资源+方便控制(操控公共资源)

此时,需要考虑到线程安全的问题。(共享资源加锁 或者让线程也有自己的资源)
单例模式可以分为 懒汉式 和 饿汉式 ,两者之间的区别在于创建实例的时间不同。

懒汉式:系统运行中,实例并不存在,只有当需要使用该实例时,才会去创建并使用实例。这种方式要考虑线程安全。
饿汉式:系统一运行,就初始化创建实例,当需要时,直接调用即可。这种方式本身就线程安全,没有多线程的线程安全问题。

2、互斥锁

互斥锁是一种特殊的二值性信号量,用于实现对共享资源的独占式处理。在任一时刻,互斥锁的状态只有两种:开锁或闭锁。当有任务(或线程)持有时,互斥锁处于闭锁状态,该任务获得互斥锁的所有权;当任务释放互斥锁时,它变为开锁状态,任务失去所有权。
作用:通过互斥锁,可以确保在任何给定的时间点,只有一个线程能够获得对临界区(即被互斥锁保护的资源区域)资源的访问权,从而避免多个线程同时访问和修改共享资源导致的数据竞争和不一致问题

临界资源:多线程中都能访问到的资源
临界区:每个线程内部,访问临界资源的代码,就叫临界区
1)初始化锁。
iht pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);

2)阻塞加锁,若互斥锁已经被其它线程上锁,则调用者一直阻塞等待,直到被解锁后才上锁。
int pthread_mutex_lock(pthread_mutex_t *mutex);

3)非阻塞加锁/若互斥锁未加锁,则上锁,若互斥锁已加锁,则函数立即返回失败。
int pthread_mutex_trylock(pthread_mutex_t*mutex);

4)解锁。
int pthread_mutex_unlock(pthread_mutex_t*mutex);

5)销毁锁, 释放资源(锁也是资源)。
int pthread_mutex_destroy(pthread_mutex_t *mutex);
#include<stdio.h>
#include<pthread.h>
#include<string.h>

char buffer[101];
pthread_mutex_t mutex;//声明互斥锁

void*pthfun(void*arg)
{
	for(int i=0;i<3;i++){
		printf("%ld:lock....\n",(long)arg);
		pthread_mutex_lock(&mutex);//加锁
		printf("%ld:lock is ok\n",(long)arg);
		sprintf(buffer,"%ld %d",pthread_self(),i);
		sleep(5);
		pthread_mutex_unlock(&mutex);//解锁
		printf("%ld:unlock....\n",(long)arg);
		usleep(100);
	}
}
int main()
{
	pthread_mutex_init(&mutex,NULL);//初始化锁

	pthread_t pthid1,pthid2;
	pthread_create(&pthid1,NULL,pthfun,(void*)1);
	pthread_create(&pthid1,NULL,pthfun,(void*)2);
	
	pthread_join(pthid1,NULL);
	pthread_join(pthid2,NULL);
	
	pthread_mutex_destroy(&mutex); //销毁锁
	
	return 0;
}

io复用使用多线程服务器

互斥锁实现数据库连接池(或者线程池)

先初始化数据库连接池,每个连接服务器的客户端需要使用数据库,就从连接池里拿一个数据库连接(也拿了锁),每次释放连接时也释放锁。
除此之外,还可以利用事务的特性保证线程安全。

  1. 原子性(Atomic)
    原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 一个事务包含多个操作,这些操作要么全部执行,要么全都不执行
  2. 隔离性(Isolation)
    并发事务之间互相影响的程度,比如一个事务会不会读取到另一个未提交的事务修改的数据。事务的隔离性

3、条件变量

条件变量用来阻塞一个线程,直到其它的线程通知它条件已满足为止。
条件变量看似简单,与互斥锁同时使用时非常巧妙。生产消费者模型(高速缓存队列)。

1)初始化条件变量
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);

2)阻塞等待
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);

3)超时等待
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex*mutex, const timespecabstime);

4)唤醒一个等待该条件的线程
int pthread_cond_signal(pthread_cond_t *cond);

5)唤醒全部等待该条件的所有线程
int pthread_cond_broadcast(pthread_cond_t *cond);

6)销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<unistd.h>
#include<signal.h>

char buffer[101];
pthread_mutex_t mutex;//声明互斥锁
pthread_cond_t cond; //声明条件变量

void*pthfun1(void*arg)
{
	while(1){
		pthread_cond_wait(&cond,&mutex);
		printf("线程1唤醒\n");
	}
}
void*pthfun2(void*arg)
{
	while(1){
		pthread_cond_wait(&cond,&mutex);
		printf("线程2唤醒\n");
	}
}
void func(int sig)
{
	pthread_cond_signal(&cond);
	
	//pthread_cond_broadcast(&cond);
}
int main()
{
	signal(15,func);
	pthread_mutex_init(&mutex,NULL);
	pthread_cond_init(&cond,NULL);
	
	pthread_t pthid1,pthid2;
	pthread_create(&pthid1,NULL,pthfun1,NULL);
	pthread_create(&pthid1,NULL,pthfun2,NULL);
	
	pthread_join(pthid1,NULL);
	pthread_join(pthid2,NULL);
	
	pthread_mutex_destroy(&mutex); //销毁锁
	
	return 0;
}

在这里插入图片描述

条件变量和互斥锁

pthread_cond_wait(&cond,&mutex);发生了这些步骤(释放锁,等条件变量,加锁)

  1. 释放了互斥锁
  2. 等待条件
  3. 条件被触发、 互斥锁加锁 第3步是原子操作
#include<stdio.h>
#include<pthread.h>
#include<string.h>

#include<unistd.h>
#include<signal.h>

char buffer[101];
pthread_mutex_t mutex;//声明互斥锁
pthread_cond_t cond; //声明条件变量

void*pthfun1(void*arg)
{
	while(1){
		pthread_mutex_lock(&mutex);  //加锁
		printf("线程1开始等待条件\n");
		pthread_cond_wait(&cond,&mutex);
		printf("线程1唤醒\n");
		pthread_mutex_unlock(&mutex);
	}
}
void*pthfun2(void*arg)
{
	while(1){
		pthread_mutex_lock(&mutex);
		printf("线程2开始等待条件\n");
		pthread_cond_wait(&cond,&mutex);
		printf("线程2唤醒\n");
		pthread_mutex_unlock(&mutex);
	}
}
void func(int sig)
{
	pthread_cond_signal(&cond);
	
	//pthread_cond_broadcast(&cond);
}
int main()
{
	signal(15,func);
	pthread_mutex_init(&mutex,NULL);
	pthread_cond_init(&cond,NULL);
	
	pthread_t pthid1,pthid2;
	pthread_create(&pthid1,NULL,pthfun1,NULL);
	pthread_create(&pthid1,NULL,pthfun2,NULL);
	
	pthread_join(pthid1,NULL);
	pthread_join(pthid2,NULL);
	
	pthread_mutex_destroy(&mutex); //销毁锁
	
	return 0;
}

在这里插入图片描述

实现高速缓存

4、信号量

信号量是一个整数计数器,其数值可以用于表示空闲临界资源的数量。
当有进程释放资源时,信号量增加,表示可用资源数增加;当有进程申请到资源时,信号量减少,表示可用资源数减少。

1)初始化信号量。   
int sem_init(sem_t*sem, int pshared,unsigned int value);
pshared :指定信号量的作用域。其值可以是 0 或非 0(通常是 1)。如果 pshared 的值为 0,则表示信号量仅在同一进程的线程之间共享。即,它用于
线程间的同步。如果 pshared 的值为非 0(通常是 1),则表示信号量可以在多个进程之间共享。但是,需要注意的是,要使得信号量在多个进程间共
享,可能需要将其放置在一个由这些进程共享的内存区域中(如通过 mmap 创建的映射区域或使用 shmget 创建的共享内存段)
value:指定了信号量的初始值。信号量的值表示可用资源的数量

2)等待信号量,如果信号量的值为0则等待,如果信号量大于0则返回,同时对值做减1操作。
int sem_wait(sem_t*sem);
int sem_trywait(sem_t*sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

3)释放信号量,信号量加1操作。
int sem post(sem_t *sem);

4)获取信号量的值。
int sem_getvalue(sem_t *sem, int *sval);

5)销毁信号量,释放资源。
int sem_destroy(sem_t *sem);
  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值