简介
- 多线程共享一个进程的地址空间,多线程间通信容易进行,但是多线程同时访问共享对象时需要引入同步和互斥机制.
- 同一个进程的线程共享进程内的绝大部分资源,当一段访问这些共享资源的代码块,有可能被多个线程执行,那么这块代码就被叫做临界区.
- 当有多个线程并发的在临界区执行时,程序的执行结果会出现不确定性,这种情况被叫做静态条件.
一个demo代码
//
// Created by wuxiao on 17-5-29.
//
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#ifndef handle_error_en
#define handle_error_en(en,msg)\
errno = en;perror(msg);exit(EXIT_FAILURE);
#endif
//变量名等规则这里先不考虑
//多线程共享资源
static int global = 0;
static void *thread_routine(void *arg)
{
int loc,j;
for(j = 0; j < 10000000;j++)
{
loc = global;
loc++;
global = loc;
}
return nullptr;
}
int main(int argc,char* argv[])
{
pthread_t t1,t2;
int result;
//创建两个线程,并修改共享资源global
result = pthread_create(&t1,NULL,thread_routine,NULL);
result += pthread_create(&t2,NULL,thread_routine,NULL);
if(result != 0)
{
handle_error_en(result,"pthread_create");
}
//等待子线程退出
result = pthread_join(t1, nullptr);
result += pthread_join(t2, nullptr);
if(result != 0)
{
handle_error_en(result,"pthread_join");
}
printf("global = %d\n",global);
exit(EXIT_SUCCESS);
}
- 发现共享变量不是预期值,原因就是进入了静态条件,需要保证多个线程在临界区时互斥的,常用方法是加互斥锁
- 锁分静态和动态:
/ 静态初始化一个全局的互斥锁
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
#include <pthread.h>
// 动态分配一个互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
// 释放动态分配的互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
- 加锁和解锁:
#include <pthread.h>
// 持有互斥锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
// 释放互斥锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
- 加锁后的demo代码
//
// Created by wuxiao on 17-5-29.
//
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#ifndef handle_error_en
#define handle_error_en(en,msg)\
errno = en;perror(msg);exit(EXIT_FAILURE);
#endif
//变量名等规则这里先不考虑
//多线程共享资源
static int global = 0;
//静态锁
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static void *thread_routine(void *arg)
{
int loc,j;
for(j = 0; j < 10000000;j++)
{
//加锁
pthread_mutex_lock(&mtx);
loc = global;
loc++;
global = loc;
//解锁
pthread_mutex_unlock(&mtx);
}
return nullptr;
}
int main(int argc,char* argv[])
{
pthread_t t1,t2;
int result;
//创建两个线程,并修改共享资源global
result = pthread_create(&t1,NULL,thread_routine,NULL);
result += pthread_create(&t2,NULL,thread_routine,NULL);
if(result != 0)
{
handle_error_en(result,"pthread_create");
}
//等待子线程退出
result = pthread_join(t1, nullptr);
result += pthread_join(t2, nullptr);
if(result != 0)
{
handle_error_en(result,"pthread_join");
}
printf("global = %d\n",global);
exit(EXIT_SUCCESS);
}
线程同步
- 加解锁一般是为了解决竞态条件的问题,但是有时候我们需要让线程按序执行,这种确保多线程间按照先后顺序执行的技术,就叫做线程同步.
- 条件变量是线程同步的主要手段,例如线程B调用条件变量让接口阻塞,线程A在处理完资源后,通过条件变量接口来环境等待资源的线程B(有点像中断的感觉..)
- 条件变量初始化分静态和动态
// 与互斥锁类似静态初始化一个全局的条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
//动态
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
- 通知和等待条件变量
include <pthread.h>
// 等待一个指定的条件变量
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
// 唤醒一个等待该条件变量的线程
int pthread_cond_signal(pthread_cond_t *cond);
// 唤醒所有等待该条件变量的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
- demo代码:
//
// Created by wuxiao on 17-5-29.
//
#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
//硬编码,命名等问题先不管
pthread_t threads[2];
char writer_char[2] = {'A','B'};
//锁和条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
//全局写入次数变量
#define MAX_WRITE_TIMES 10
int write_times = 0;
struct file_res
{
//当前文件写入的线程
pthread_t *writer;
//文件描述符
int fd;
}file_res =
{
.writer = &threads[0],
};
//两个线程的入口函数
void* writer_routine(void *arg)
{
int index = (intptr_t)arg;
size_t i = 0;
int next_index = 0;
printf("thread %d is running,will write %c\n",index,writer_char[index]);
while(1)
{
//如果已经到达最大写入次数
if(write_times >= MAX_WRITE_TIMES)
{
//结束当前线程
pthread_exit(NULL);
}
if(pthread_mutex_lock(&mutex) != 0)
{
exit(EXIT_FAILURE);
}
for(;;)
{
//如果当前线程可写入文件,写文件
if(&threads[index] == file_res.writer)
{
write(file_res.fd,&writer_char[index],sizeof(writer_char[index]));
//更新文件的写入线程
next_index = (index + 1)%2;
file_res.writer = &threads[next_index];
write_times++;
//跳出死循环,通知下一个线程进场
break;
}
//当前线程不可写,等待下一个线程唤醒
pthread_cond_wait(&cond,&mutex);
}
//解锁并唤醒下一个线程
if(pthread_mutex_unlock(&mutex) != 0)
{
exit(EXIT_FAILURE);
}
pthread_cond_signal(&cond);
}
}
int main(int argc,char* argv[])
{
//创建文件
char file_name[] = "file";
if(file_res.fd = open(file_name,O_RDWR|O_CREAT|O_TRUNC,0666) < 0)
{
printf("open %s fail\n",file_name);
exit(EXIT_FAILURE);
}
//创建线程1和2
size_t i;
for(i = 0;i < sizeof(threads)/sizeof(pthread_t);i++)
{
if(pthread_create(&threads[i],NULL,writer_routine,(void*)(intptr_t)i) != 0)
{
printf("create thread %zu fail\n", i);
exit(EXIT_FAILURE);
}
}
pthread_exit(NULL);
}