1、线程同步的定义
线程同步不是一起、相同,而是协调、协同的意思。
1)按预定的先后次序进行运行,如:您说完,我再说;线程A生成数据后交给线程B处理;
2)公共资源同一时刻只能被一个线程使用;共享数据在同一时刻只能被一个线程修改,以保证数据的完整性。
线程安全的3大特性
1、原子性
原子性是指操作是不可分的。其表现在于对于共享变量的某些操作,应该是不可分的,必须连续完成。
2、可见性
可见性是指一个线程对共享变量的修改,另外一个线程能够立刻看到;
基于互斥锁、条件变量、信号量、自旋锁、读写锁。
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复用使用多线程服务器
互斥锁实现数据库连接池(或者线程池)
先初始化数据库连接池,每个连接服务器的客户端需要使用数据库,就从连接池里拿一个数据库连接(也拿了锁),每次释放连接时也释放锁。
除此之外,还可以利用事务的特性保证线程安全。
- 原子性(Atomic)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 一个事务包含多个操作,这些操作要么全部执行,要么全都不执行 - 隔离性(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);
发生了这些步骤(释放锁,等条件变量,加锁)
- 释放了互斥锁
- 等待条件
- 条件被触发、 互斥锁加锁 第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);