一:线程同步
什么是线程??
和多进程相比,多线程是一种比较节省资源的多任务操作方式。启动一个新的进程须 配 给 它独立的地址空间,每个进程都有自己的堆栈段和数据段,子进程要拷贝父进程的资源,系统开销比较高,进行数据的传递只能通过进程间通信的方式进行。在同一个进程中,可以运行多个线程,运行于同一个进程中的多个线程,它们彼此之间使用相同的地址空间,及共享同一个进程的全局变量和对象,启动一个线程所消耗的资源比启动一个进程所消耗的资源要少。
什么是线程同步?是几个线程一起运行?是几个线程一起停止?
在上面我们提到了运行于同一个进程中的多个线程共享同一个进程的全局变量和对象,当一个线程在访问进程中的某个全局变量时另一个线程恰巧将这个全局变量更改了,那么这个线程访问的数据就会有问题就不是我们要访问数据,那么怎么解决这个问题呢?如果将多个线程共享的数据区加一个锁,当有线程访问的时候这个线程获得这个锁,其他线程就无法访问这块数据区,就可以解决这个问题了,也就达到了多个线程协同步调的作用。
二:互斥锁
互斥锁机制是同一时刻只允许一个线程对共享的资源进行操作
1:初始化锁
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutex_attr_t *mutexattr);
参数 mutex是锁
参数mutexattr是锁的属性如果为NULL则使用缺省属性其他参数(见下)
互斥锁的属性在创建锁的时候指定,当资源被某线程锁住的时候,其它的线程在试图加锁时表现将不同。当前有四个值可供选择:
(1)PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
(2)PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。
(3)PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。
(4)PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,等待解锁后重新竞争
2:加锁
int pthread_mutex_lock(pthread_mutex *mutex);
如果锁没有线程得到那么线程就获得该锁,否则线程就等待获得该锁
3:解锁
int pthread_mutex_unlock(pthread_mutex *mutex);
把线程持有的该锁释放
4:销毁锁
int pthread_mutex_destroy(pthread_mutex *mutex);
销毁锁之前,锁必需是空闲状态及(unlock)状态
5:代码演示
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <pthread.h>
pthread_mutex_t mutex; // 申明一个互斥锁
// 线程函数
void *pth_main(void *arg)
{
int pno=(long)arg; // 线程编号
char strbuffer[1024];
for (int ii=0;ii<3;ii++)
{
pthread_mutex_lock(&mutex); // 加锁
memset(strbuffer,0,sizeof(strbuffer));
sprintf(strbuffer,"线程%d:这是第%d个超级女生,编号%03d。",pno,ii+1,ii+1);
memset(strbuffer,0,sizeof(strbuffer));
pthread_mutex_unlock(&mutex); // 释放锁
usleep(100); // usleep(100),否则其它的线程无法获得锁。
}
pthread_exit(0);
}
int main()
{
pthread_mutex_init(&mutex,0); // 创建锁
pthread_t pthid1,pthid2;
pthread_create(&pthid1,NULL,pth_main,(void*)1); // 创建第一个线程
pthread_create(&pthid2,NULL,pth_main,(void*)2); // 创建第二个线程
pthread_join(pthid1,NULL); // 等待线程1退出。
pthread_join(pthid2,NULL); // 等待线程2退出。
pthread_mutex_destroy(&mutex); // 销毁锁
}
6:互斥锁存在优先唤醒问题
优先唤醒问题就是当有多个线程同时操作一块资源的时候,有一个线程先获得了锁释放了锁以后如果处理的业务不多线程很快就执行完程序 ,那么这个线程将继续获得这个锁
三:线程条件变量
与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用
1:条件变量初始化
int pthread_cond_init (pthread_cond_t *cond, const pthread_condattr_t *attr);
参数cond条件变量指针
参数attr条件变量属性设置为NULL默认为缺省属性
2: 唤醒一个等待线程及唤醒一个等待者
int pthread_cond_signal (pthread_cond_t *cond);
参数:cond, 条件变量指针
3:唤醒所有等待该条件变量的线程及 广播条件变量
int pthread_cond_broadcast (pthread_cond_t *cond);
参数:cond, 条件变量指针
4:等待条件变量被唤醒
int pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex);
该函数执行的操作为
(1)线程条件变量和互斥锁一起用的时候
(2)先解锁
(3)等待唤醒
(4)唤醒以后再加锁(原子操作)
参数:cond, 条件变量指针
pthread_mutex_t *mutex 互斥量及互斥锁
5:释放/销毁条件变量
int pthread_cond_destroy (pthread_cond_t *cond);
参数:cond, 条件变量指针
6:等待条件变量/超时被唤醒直到由一个信号或广播,或绝对时间abstime到 * 才唤醒该线程
int pthread_cond_timedwait (pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
参数:cond, 条件变量指针
pthread_mutex_t *mutex 互斥量
const struct timespec *abstime 等待被唤醒的绝对超时时间
7:代码演示
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t mutex;
pthread_cond_t cond;
void *thread1(void *arg)
{
while (1) {
printf("thread1 is running\n");
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);
printf("thread1 applied the condition\n");
pthread_mutex_unlock(&mutex);
sleep(4);
}
}
void *thread2(void *arg)
{
while (1) {
printf("thread2 is running\n");
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);
printf("thread2 applied the condition\n");
pthread_mutex_unlock(&mutex);
sleep(2);
}
}
int main()
{
pthread_t thid1, thid2;
printf("condition variable study!\n");
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
pthread_create(&thid1, NULL, (void *)thread1, NULL);
pthread_create(&thid2, NULL, (void *)thread2, NULL);
do {
pthread_cond_signal(&cond);
sleep(1);
} while (1);
return 0;
}
四:(互斥锁,条件变量的应用)生产者消费者模式
生产消费者模式就是多个线程产生请求将数据保存在数据缓存,另外有多个线程来处理产生的请求
图像上的生产者就是多个客户端的连入对服务器的请求,服务器将客户端的请求放入数据缓存,由于有多个客户端的请求,而请求是放在同一个数据缓存,如果多个客户端同时操作的是数据缓存的同一块内存,那么会导致一个请求被另一个请求覆盖存在资源竞争的问题,那么互斥锁将很好的解决这种问题,因为互斥锁可以协调线程同步,消费者的工作是对客户端请求的处理,同样也存在资源竞争的关系,那么互斥锁也将很好的解决这种问题。
消费者在等待生产者产生请求来处理请求,如果消费者用循环来一直监视生产者是否产生请求,那么循环将会一直占用cpu资源,解决这个问题的方法就是用线程条件变量,当有生产者产生请求的是时候就通知消费者有请求产生,消费者就去处理请求,这样就完美解决了这个问题。
五:高速缓存的实现(生产消费者模型实现)
1:高速缓存代码实现
#include<stdio.h>
#include<string.h>
#include<signal.h>
#include<vector>
#include<unistd.h>
#include<pthread.h>
using namespace std;
typedef struct message_t{
int msgId;
char messages[64];
}message;
int msgId=0;
vector<message>vcache; //用容器实现缓存
pthread_cond_t cond; //线程条件变量
pthread_mutex_t mutex; //互斥锁
void* outCache(void *arg){
message msg_t;
while(1){
//加锁
pthread_mutex_lock(&mutex);
//如果缓存为空 等待
while(vcache.size()==0){ //条件虚假唤醒
pthread_cond_wait(&cond,&mutex);
}
memcpy(&msg_t,&vcache[0],sizeof(message));
vcache.erase(vcache.begin());
//解锁
pthread_mutex_unlock(&mutex);
printf("phid:%ld msgId:%d\n",pthread_self(),msg_t.msgId);
usleep(100);
}
}
void inCache(int arg){
message msg_t;
memset(&msg_t,0,sizeof(message));
//加锁
pthread_mutex_lock(&mutex);
msg_t.msgId=msgId++;
vcache.push_back(msg_t);
msg_t.msgId=msgId++;
vcache.push_back(msg_t);
msg_t.msgId=msgId++;
vcache.push_back(msg_t);
//解锁
pthread_mutex_unlock(&mutex);
//给消费者发信号
pthread_cond_broadcast(&cond);
}
int main(){
pthread_t thread1,thread2,thread3;
pthread_cond_init(&cond,NULL);
pthread_mutex_init(&mutex,NULL);
signal(SIGUSR1,inCache);
//创建三个消费者
pthread_create(&thread1,NULL,outCache,NULL);
pthread_create(&thread2,NULL,outCache,NULL);
pthread_create(&thread3,NULL,outCache,NULL);
pthread_join(thread1,NULL);
pthread_join(thread2,NULL);
pthread_join(thread3,NULL);
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
return 0;
}
(1)在消费者线程处理函数中有usleep()函数是为了让线程休眠以便其他线程也可以处理请求,因为在上文我们提到互斥锁有优先唤醒问题,如果不休眠所有请求都将会被一个线程执行完
(2)在在消费者线程处理函数中有判断缓存是否为空的循环,为什么不用if语句判断呢?因为互斥锁有优先唤醒问题,如果不是循环判断,当一个线程把所有请求处理完了,然而其他线程也被唤醒了,那么其他线程处理的时候会出现错误
(3)虚假条件唤醒是由于系统发出信号而不是生产者发出的信号导致消费者收到错误信息