线程
【1】线程的概念
线程:是程序并发执行多种任务的机制;
并发:多个任务同时进程;(cpu以ms级别的速度进程调度,切换进程、线程);
进程的上下文切换:
上下文--->运行性一个程序所需的所有资源;
切换上下文--->替换原来的内容,是一个耗时操作。
为了提高系统的性能,许多操作系统引入了一个轻量级进程的概念,也被被称之为线程。
线程是属于进程的。每一个进程至少需要一个线程作为指令执行体。线程运行在进程空间内。
多线程:一个进程,可以运行多个线程。
同一进程下的线程,共享该进程的内存地址(每个线程共享其附属进程的所有资源);
资源分配
线程与线程之间共享进程的:
1)数据段(全局变量,静态变量,堆)
2)正文段
3)文件描述符
不共享:
1)栈区:每个线程都有自己的栈区,存放各自的局部变量(每个人格都有自己独立的记忆)
进程与线程的区别
1)进程之间,享有独立的虚拟空间,是独立的个体;
因此,进程与进程之间要实现数据传输,需要引入进程间通信机制。
2)属于同一进程下的线程,共享进程的虚拟地址空间,不需要通信机制。
3)进程是资源管理的最小单位,线程是程序执行的最小单位。
4)创建子进程需要克隆父进程的所有资源,但是创建线程不需要,因为本身就共享进程的资源。
所以,创建多线程的效率比创建子进程的效率高。
【2】线程常用的函数
man 3 卷
man 3 pthread_create
1)安装库的man手册
$ sudo apt-get install manpages-posix manpages-posix-dev
2)pthread_create
功能:创建线程;
头文件:
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
参数:
pthread_t *thread:存储创建成功后的线程id;
pthread_attr_t *attr:线程属性,一般填NULL,代表默认属性;
void *(*start_routine) (void *):函数指针,需要传入一个函数指针或函数地址。又称之为回调函数。
可以指向:返回值是void*,且参数列表是void*类型的函数。 void* handler(void*arg){};
void *arg:传入回调函数的参数。
返回值:
成功,返回0;
失败,返回错误码,errno;
Compile and link with -pthread.
补充:函数指针问题
void printf_shanghai(int i)
{
printf("%d 上海\n",i);
}
void printf_beijing(int i)
{
printf("%d beijing\n",i);
}
void printf_area(void (*p)(int), int j)
{
p(j);
}
int main(int argc, const char *argv[])
{
void (*pfunc)(int);
pfunc = printf_beijing;
printf_area(printf_beijing, 1);
return 0;
}
例子
typedef struct
{
char c;
int b;
}_test;
int num = 10;
void* handler(void*arg)
{
_test e = *(_test*)arg;
printf("1 %c %d\n",e.c, e.b);
/*
num = 11;
printf("1 num = %d\n", num);
*/
//printf("1 a=%d\n",a);
while(1)
{
printf("pthread\n");
sleep(1);
}
return NULL;
}
int main(int argc, const char *argv[])
{
pthread_t tid;
printf("num = %d\n", num);
int a = 10;
_test t = {'a', 20};
if(pthread_create(&tid, NULL, handler,(void*)&t)!=0)
{
perror("pthread_create");
return -1;
}
printf("创建成功 tid=%lu\n", tid);
sleep(1);
/*
printf("num = %d\n", num);
*/
printf("a=%d\n",a);
while(1)
{
printf("主线程\n");
sleep(1);
}
return 0;
}
注意:
- 线程是依附于进程,如果进程(主线程)退出,则同一进程下的线程也会强制退出。
- 分支线程退出,不会影响其余线程的运行。
- 主线程和分支线程是并行的,谁先运行不确定,主要看cpu的调度。
3)pthread_self
功能:获取当前线程的id号;
头文件:
#include <pthread.h>
原型:
pthread_t pthread_self(void);
例子
void* handler2(void* arg)
{
while(1)
{
printf("分线程 %lu\n", pthread_self());
sleep(1);
}
return NULL;
}
4)pthread_exit
功能:退出当前线程。 (和进程中的exit类似);
头文件:
#include <pthread.h>
原型:
void pthread_exit(void *retval);
参数:
void *retval:指向存储线程退出的状态值,可以是任意类型的数据地址,也可以是NULL;
可以被pthread_join函数接收
注意:
没有pthread_join,线程的资源是没有被回收的,类似于进程的僵尸进程。
5)pthread_join
功能:阻塞等待指定分支线程的退出.回收线程资源; (类似进程中的wait);
头文件:
#include <pthread.h>
原型:
int pthread_join(pthread_t thread, void **retval);
参数:
pthread_t thread:指定要等待的线程;
void **retval:存储pthread_exit的参数地址。如果不想接收,可以填NULL;
返回值:
成功,返回0;
失败,返回错误码,errno;
例子
//方式2
int res = 12;
void* handler(void* arg)
{
int i = 0;
while(i<3)
{
printf("分线程 %lu\n",pthread_self());
i++;
sleep(1);
}
//方式1:
static int j = 10;
// int j = 10;
//pthread_exit(&res);
//方式3;
pthread_exit((void*)1);
}
int main(int argc, const char *argv[])
{
pthread_t tid = 0;
pthread_create(&tid, NULL, handler, NULL);
/*
int *status;
pthread_join(tid, (void**)&status);
printf("status=%d\n",*status);
*/
//方式3
int status = 0;
pthread_join(tid, (void**)&status);
printf("status=%d\n",status);
printf("主线程 %lu %lu\n",pthread_self(), tid);
return 0;
}
小练习
拷贝一张图片,实现A线程拷贝前半部分,B线程拷贝后后半部分。
提示:B线程先sleep(1)。
#include <stdio.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
int fd_r, fd_w;
//拷贝前半部分
void* copy_front(void*arg)
{
int size = *(int*)arg;
lseek(fd_r, 0, SEEK_SET);
lseek(fd_w, 0, SEEK_SET);
int i = 0;
char c = 0;
for(i=0; i<size/2; i++)
{
int ret = read(fd_r, &c, 1);
if(ret < 0)
{
perror("read");
pthread_exit(NULL);
}
ret = write(fd_w, &c, 1);
if(ret < 0)
{
perror("write");
pthread_exit(NULL);
}
}
pthread_exit(NULL);
}
//拷贝后半部分
void* copy_end(void*arg)
{
sleep(1);
int size = *(int*)arg;
lseek(fd_r, size/2, SEEK_SET);
lseek(fd_w, size/2, SEEK_SET);
int i = size/2;
int c = 0;
for(; i<size; i++)
{
int ret = read(fd_r, &c, 1);
if(ret < 0)
{
perror("read");
pthread_exit(NULL);
}
else if(0 == ret)
{
break;
}
ret = write(fd_w, &c, 1);
if(ret < 0)
{
perror("write");
pthread_exit(NULL);
}
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
//打开文件
fd_r = open("1.png", O_RDONLY);
fd_w = open("2.png", O_WRONLY|O_CREAT|O_TRUNC, 0777);
if(fd_r<0 || fd_w<0)
{
perror("open");
exit(1);
}
//计算文件大小
int size = lseek(fd_r, 0, SEEK_END);
printf("size = %d %d\n",size, __LINE__);
//创建线程×2
pthread_t tid1, tid2;
if(pthread_create(&tid1, NULL, copy_front, (void*)&size) !=0)
{
fprintf(stderr, "line=%d ",__LINE__);
perror("pthread_create");
exit(2);
}
if(pthread_create(&tid2, NULL, copy_end, (void*)&size) !=0)
{
fprintf(stderr, "line=%d ",__LINE__);
perror("pthread_create");
exit(2);
}
//等待分支线程退出
pthread_join(tid1, NULL);
printf("前半部分拷贝完毕\n");
pthread_join(tid2, NULL);
printf("后半部分拷贝完毕\n");
return 0;
}
6)pthread_cancel
功能:指定一个线程退出;
头文件:
#include <pthread.h>
原型:
int pthread_cancel(pthread_t thread);
参数:
pthread_t thread:指定要退出的线程。只是发送退出请求,发送成功并不意味着线程退出.
返回值:
成功,返回0;
失败,返回errno;
例子
#include <stdio.h>
#include <pthread.h>
pthread_t tid1,tid2;
void* handler1(void* arg)
{
int i = 0;
while(i<3)
{
printf("1号 %lu\n", pthread_self());
sleep(1);
i++;
}
pthread_cancel(tid2);
pthread_exit(NULL);
}
void* handler2(void* arg)
{
while(1)
{
printf("2号 %lu\n", pthread_self());
// sleep(1);
}
pthread_exit((void*)100);
}
int main(int argc, const char *argv[])
{
pthread_create(&tid1, NULL, handler1, NULL);
pthread_create(&tid2, NULL, handler2, NULL);
int status;
pthread_join(tid2, (void**)&status);
printf("tid2退出 %d\n",status);
pthread_join(tid1, NULL);
printf("tid1退出\n");
return 0;
}
7)pthread_detach
功能:分离线程,线程退出后,自动回收线程资源;
头文件:
#include <pthread.h>
原型:
int pthread_detach(pthread_t thread);
参数:
pthread_t thread:指定要分离的线程;
返回值:
成功,返回0;
失败,返回errno;
例子
void* handler2(void* arg)
{
pthread_detach(pthread_self());
while(1)
{
printf("2号 %lu\n", pthread_self());
sleep(1);
}
pthread_exit((void*)100);
}
【3】线程的同步互斥机制
临界资源(共享资源):多个任务并发执行时,访问同一个资源,我们将该资源称之为临界资源
临界区:访问临界资源(共享资源)的操作代码,称之为临界区
线程之间如果要进行通讯,要引入同步互斥机制,避免产生竞态。保证任何一个时刻,只有一个线程处理共享资源。
同步互斥机制:
1. 互斥锁(pthread_mutex_t)
2. 条件变量(pthread_cond_t)
3. 信号量(sem_t)
1. 互斥锁
1)工作原理
1. 对于要访问临界资源的线程,在访问前,先申请互斥锁。
如果上锁成功,则执行临界区代码,直到退出临界区,解开互斥锁。
如果申请上锁失败,互斥锁被别的线程占用,那么该线程进入睡眠,等待互斥锁解锁。
2. 互斥锁将临界区锁住,是为了保证临界区的完整性。
3. 互斥锁又称之为二值信号量
2)pthread_mutex_init
功能:创建并初始化互斥锁;
头文件:
#include <pthread.h>
原型:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
参数:
pthread_mutex_t * restrict mutex:存储申请及初始化后的互斥锁。
pthread_mutexattr_t *restrict attr:互斥锁属性,一般填NULL;
返回值:
成功,返回0;
失败,返回errno;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
3)pthread_mutex_destroy
功能:销毁互斥锁;
头文件:
#include <pthread.h>
原型:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数:
pthread_mutex_t * mutex:指定要销毁的互斥锁。
返回值:
成功,返回0;
失败,返回errno;
4)pthread_mutex_lock
功能:上锁;
头文件:
#include <pthread.h>
原型:
int pthread_mutex_lock(pthread_mutex_t *mutex);
参数:
pthread_mutex_t *mutex:指定需要上锁的互斥锁;
返回值:
成功,返回0;
失败,返回errno;
5)pthread_mutex_unlock
功能:解锁;
头文件:
#include <pthread.h>
原型:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数:
pthread_mutex_t *mutex:指定需要解开的互斥锁;
返回值:
成功,返回0;
失败,返回errno;
小练习
/*创建两个线程,一个线程实现倒置,一个线程实现打印*/
//用宏的方式初始化 方法2
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//临界资源
char buf[20] = "AAAAA|BBBBB";
void* inver_handler(void* arg)
{
char temp =0;
while(1)
{
//上锁
pthread_mutex_lock(&mutex);
/**********临界区***************/
char* start = buf;
char* end = buf+strlen(buf)-1;
while(start < end)
{
temp = *start;
*start = *end;
*end = temp;
start++;
end--;
}
/**********临界区***************/
//解锁
pthread_mutex_unlock(&mutex);
}
pthread_exit(NULL);
}
void* printf_handler(void* arg)
{
while(1)
{
//上锁
pthread_mutex_lock(&mutex);
/**********临界区***************/
printf("%s\n",buf);
/**********临界区***************/
//解锁
pthread_mutex_unlock(&mutex);
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
/*
//申请互斥锁 方式1
if(pthread_mutex_init(&mutex, NULL) !=0)
{
perror("pthread_mutex_init");
return -1;
}
*/
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, inver_handler, NULL);
pthread_create(&tid2, NULL, printf_handler, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
小练习
用互斥锁实现,A线程拷贝前半部分,B线程拷贝后半部分,
不使用sleep(1);而且俩文件只能打开一次
#include <stdio.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
int fd_r, fd_w;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//拷贝前半部分
void* copy_front(void*arg)
{
int size = *(int*)arg;
/**********临界区***************/
pthread_mutex_lock(&mutex);
lseek(fd_r, 0, SEEK_SET);
lseek(fd_w, 0, SEEK_SET);
int i = 0;
char c = 0;
for(i=0; i<size/2; i++)
{
int ret = read(fd_r, &c, 1);
if(ret < 0)
{
perror("read");
pthread_exit(NULL);
}
ret = write(fd_w, &c, 1);
if(ret < 0)
{
perror("write");
pthread_exit(NULL);
}
}
pthread_mutex_unlock(&mutex);
/**********临界区***************/
pthread_exit(NULL);
}
//拷贝后半部分
void* copy_end(void*arg)
{
int size = *(int*)arg;
/**********临界区***************/
pthread_mutex_lock(&mutex);
lseek(fd_r, size/2, SEEK_SET);
lseek(fd_w, size/2, SEEK_SET);
int i = size/2;
int c = 0;
for(; i<size; i++)
{
int ret = read(fd_r, &c, 1);
if(ret < 0)
{
perror("read");
pthread_exit(NULL);
}
else if(0 == ret)
{
break;
}
ret = write(fd_w, &c, 1);
if(ret < 0)
{
perror("write");
pthread_exit(NULL);
}
}
pthread_mutex_unlock(&mutex);
/**********临界区***************/
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
//打开文件
fd_r = open("1.png", O_RDONLY);
fd_w = open("2.png", O_WRONLY|O_CREAT|O_TRUNC, 0777);
if(fd_r<0 || fd_w<0)
{
perror("open");
exit(1);
}
//计算文件大小
int size = lseek(fd_r, 0, SEEK_END);
printf("size = %d %d\n",size, __LINE__);
//创建线程×2
pthread_t tid1, tid2;
if(pthread_create(&tid1, NULL, copy_front, (void*)&size) !=0)
{
fprintf(stderr, "line=%d ",__LINE__);
perror("pthread_create");
exit(2);
}
if(pthread_create(&tid2, NULL, copy_end, (void*)&size) !=0)
{
fprintf(stderr, "line=%d ",__LINE__);
perror("pthread_create");
exit(2);
}
//等待分支线程退出
pthread_join(tid1, NULL);
printf("前半部分拷贝完毕\n");
pthread_join(tid2, NULL);
printf("后半部分拷贝完毕\n");
//销毁互斥锁
pthread_mutex_destroy(&mutex);
return 0;
}
2. 条件变量
1)工作原理
- 将不访问共享资源的线程直接睡眠
- 如果线程需要访问,则通过其他线程或进程将其唤醒。
2)pthread_cond_init
功能:创建并初始化条件变量;
头文件:
#include <pthread.h>
原型:
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
参数:
pthread_cond_t *restrict cond:存储创建并初始化后的条件变量;
const pthread_condattr_t *restrict attr:条件变量的属性,填NULL;
返回值:
成功,返回0;
失败,返回errno;
int pthread_cond_destroy(pthread_cond_t *cond);
3)pthread_cond_destroy
功能:创建并初始化条件变量;
头文件:
#include <pthread.h>
原型:
int pthread_cond_destroy(pthread_cond_t *cond);
参数:
pthread_cond_t * cond: 指定要销毁的条件变量;
返回值:
成功,返回0;
失败,返回errno;
4)pthread_cond_wait
功能:让线程进入睡眠,等待被唤醒;
头文件:
#include <pthread.h>
原型:
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
参数:
pthread_cond_t *restrict cond:指定的条件变量;
pthread_mutex_t *restrict mutex:互斥锁;
返回值:
成功,返回0;
失败,返回errno;
**该函数的步骤**:
1.解开互斥锁
2.阻塞当前线程;
3.等待被唤醒;
4.唤醒成功后,尝试上锁,
5.如果上锁成功,则从当前位置继续向下运行
6.如果没有抢到,则从当前位置继续阻塞
注意:
唤醒,且上锁成功后,是从上一次睡觉的地方继续运行的。不会从头开始运行程序。 (不会再走if(flag != n))
5)pthread_cond_signal
功能:唤醒条件变量;
头文件:
#include <pthread.h>
原型:
int pthread_cond_signal(pthread_cond_t *cond);
参数:
pthread_cond_t *cond:指定要唤醒的条件变量;
返回值:
成功,返回0;
失败,返回errno;
6)条件变量的基本格式
根据需求修改flag 和 todo something位置
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_t tid1, tid2;
int flag = 0;
void* t1_handler(void* arg)
{
while(1)
{
//上锁
pthread_mutex_lock(&mutex);
if(flag != 0)
{
//1.解锁mutex
//2.让当前线程睡眠
//3.等待线程被唤醒
pthread_cond_wait(&cond, &mutex);
//4.唤醒成功,上锁mutex
//5.如果上锁成功,则从当前位置继续向下运行
//6.如果没抢到,则在当前位置继续阻塞
}
//todo something
printf("111111\n");
//改变标志位,节目结束,换台
flag = 1;
//解锁
pthread_mutex_unlock(&mutex);
//唤醒
pthread_cond_signal(&cond);
}
pthread_exit(NULL);
}
void* t2_handler(void* arg)
{
while(1)
{
//上锁
pthread_mutex_lock(&mutex);
if(flag != 1)
{
pthread_cond_wait(&cond, &mutex);
}
//todo something
printf("222222\n");
//改变标志位
flag = 0;
//解锁
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
//创建线程
pthread_create(&tid1, NULL, t1_handler, NULL);
pthread_create(&tid2, NULL, t2_handler, NULL);
//等待线程退出
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
//销毁锁以及条件变量
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
例子
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int flag = 0; //为0代表喜洋洋与灰太狼 1代表天线宝宝
void* handler1(void* arg)
{
while(1)
{
//上锁
pthread_mutex_lock(&mutex);
if(flag != 0)
{
//1.让当前线程进入阻塞态,等待被唤醒;
//2.打开互斥锁
pthread_cond_wait(&cond, &mutex);
//3.被唤醒,上锁mutex
//4.如果上锁成功,则从当前位置继续向下运行
//5.如果没抢到,则在当前位置继续阻塞
}
printf("喜洋洋与灰太狼\n");
flag = 1;
//解锁
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);
}
pthread_exit(NULL);
}
void* handler2(void* arg)
{
while(1)
{
//上锁
pthread_mutex_lock(&mutex);
if(flag != 1)
{
//1.让当前线程进入阻塞态,等待被唤醒;
//2.打开互斥锁
pthread_cond_wait(&cond, &mutex);
//3.被唤醒,上锁mutex
}
printf("天线宝宝\n");
flag = 0;
//解锁
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);
}
pthread_exit(NULL);
}
void* handler3(void* arg)
{
while(1)
{
//上锁
pthread_mutex_lock(&mutex);
if(flag != 2)
{
//1.让当前线程进入阻塞态,等待被唤醒;
//2.打开互斥锁
pthread_cond_wait(&cond, &mutex);
//3.被唤醒,上锁mutex
//4.如果上锁成功,则从当前位置继续向下运行
//5.如果没抢到,则在当前位置继续阻塞
}
printf("铠甲勇士_______________\n");
flag = 0;
//解锁
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
pthread_t tid1, tid2, tid3;
pthread_create(&tid1, NULL, handler1, NULL);
pthread_create(&tid2, NULL, handler2, NULL);
pthread_create(&tid3, NULL, handler3, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
小练习
条件变量
1.创建两个线程和全局变量char buf[] = “hello world”;要求如下:
1)一个线程将buf字符串倒置,一个线程完成打印。
2)打印一次,倒置一次。
3)用条件变量实现
#include <stdio.h>
#include <pthread.h>
#include <string.h>
char buf[20] = "AAAAA|BBBBB";
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int flag = 0;
void* invertion(void* arg)
{
while(1)
{
//上锁
pthread_mutex_lock(&mutex);
if(flag != 1)
{
pthread_cond_wait(&cond, &mutex);
}
//倒置
char* start = buf;
char* end = buf+strlen(buf)-1;
while(start < end)
{
char temp = *start;
*start = *end;
*end = temp;
start++;
end--;
}
flag = 0;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);
}
pthread_exit(NULL);
}
void* printf_handler(void* arg)
{
while(1)
{
//上锁
pthread_mutex_lock(&mutex);
if(flag != 0)
{
pthread_cond_wait(&cond, &mutex);
}
printf("%s\n", buf);
flag = 1;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, invertion, NULL);
pthread_create(&tid2, NULL, printf_handler, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
小练习:
2.完成三个线程顺序执行,分别打印ABC.循环打印,使用条件变量实现
#include <stdio.h>
#include <pthread.h>
#include <string.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_cond_t cond1 = PTHREAD_COND_INITIALIZER;
pthread_cond_t cond2 = PTHREAD_COND_INITIALIZER;
int flag = 0;
void* handler_a(void* arg)
{
while(1)
{
//上锁
pthread_mutex_lock(&mutex);
if(flag != 0)
{
pthread_cond_wait(&cond, &mutex);
}
printf("A");
flag = 1;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond1);
}
pthread_exit(NULL);
}
void* handler_b(void* arg)
{
while(1)
{
//上锁
pthread_mutex_lock(&mutex);
if(flag != 1)
{
pthread_cond_wait(&cond1, &mutex);
}
printf("B");
flag = 2;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond2);
}
pthread_exit(NULL);
}
void* handler_c(void* arg)
{
while(1)
{
//上锁
pthread_mutex_lock(&mutex);
if(flag != 2)
{
pthread_cond_wait(&cond2, &mutex);
}
printf("C\n");
flag = 0;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
pthread_t tid1, tid2, tid3;
pthread_create(&tid1, NULL, handler_a, NULL);
pthread_create(&tid2, NULL, handler_b, NULL);
pthread_create(&tid3, NULL, handler_c, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
pthread_cond_destroy(&cond1);
pthread_cond_destroy(&cond2);
return 0;
}
3.信号量
1)工作原理
1. 对于访问共享资源的线程,都去执行获取信号量的操作。
当信号量大于0,如果获取信号量成功,则信号量-1;
当信号量为0,则获取信号量的操作会阻塞,线程进入休眠等待阶段。
2. 互斥锁又称之为二值信号量,只允许一个线程进入临界区,
3. 信号量允许多个线程同时进入临界区。
4. PV操作:实现线程、进程同步互斥的有效方式
P:通过信号量, -1操作
V:释放信号量,+1操作
2)sem_init
功能:创建并初始化信号量;
头文件:
#include <semaphore.h>
原型:
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
sem_t *sem:存储创建及初始化后的信号量;
int pshared:共享标志;
0,用于线程
1,用于进程;
unsigned int value:信号量的初始值;
返回值:
成功,返回0;
失败,返回-1,更新errno;
例子
int main(int argc, const char *argv[])
{
sem_t sem;
if(sem_init(&sem, 0, 3)<0)
{
perror("sem_init");
return -1;
}
int val = 0;
sem_getvalue(&sem, &val);
printf("val = %d\n", val);
printf("创建信号量成功\n");
sem_wait(&sem); //3-1 2
printf("P操作成功\n");
sem_getvalue(&sem, &val);
printf("val = %d\n", val);
sem_wait(&sem); //2-1 1
printf("P操作成功\n");
sem_getvalue(&sem, &val);
printf("val = %d\n", val);
sem_wait(&sem); //1-1 0
printf("P操作成功\n");
sem_getvalue(&sem, &val);
printf("val = %d\n", val);
3)sem_destroy
功能:销毁信号量;
头文件:
#include <semaphore.h>
原型:
int sem_destroy(sem_t *sem);
4)sem_wait (-1)
功能:通过信号量,信号量-1, P操作
1.如果获取前,信号量已经为0了,那么申请失败,线程阻塞;
2.如果获取前,信号量>0,则申请成功,信号量-1,该函数立即返回;
头文件:
#include <semaphore.h>
原型:
int sem_wait(sem_t *sem);
参数:
sem_t *sem:指定信号量;
返回值:
成功,返回0;
失败,返回-1,更新errno;
5)sem_getvalue
功能:获取信号量的值;
头文件:
#include <semaphore.h>
原型:
int sem_getvalue(sem_t *sem, int *sval);
参数:
sem_t *sem:指定信号量;
int *sval:存储信号量的值;
返回值:
成功,返回0;
失败,返回-1,更新errno;
6)sem_post (+1)
功能:释放信号量,信号量+1, V操作;
头文件:
#include <semaphore.h>
原型:
int sem_post(sem_t *sem);
参数:
sem_t *sem:指定要操作的信号量;
返回值:
成功,返回0;
失败,返回-1,更新errno;
例子
#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>
#include <string.h>
sem_t sem1;
sem_t sem2;
char buf[20] = "AAAAA|BBBBB";
//倒置
void* handler1(void* arg)
{
while(1)
{
//P操作 -1;
sem_wait(&sem1);
//倒置
char* start = buf;
char* end = buf+strlen(buf)-1;
while(start<end)
{
char temp = *start;
*start = *end;
*end = temp;
start++;
end--;
}
//V操作 +1;
sem_post(&sem2);
}
pthread_exit(NULL);
}
//输出
void* handler2(void* arg)
{
while(1)
{
//P操作 -1;
sem_wait(&sem2);
printf("%s\n", buf);
//V操作 +1;
sem_post(&sem1);
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
//信号量初始化
if(sem_init(&sem1, 0, 0)<0)
{
perror("sem_init");
return -1;
}
if(sem_init(&sem2, 0, 1)<0)
{
perror("sem_init");
return -1;
}
printf("创建信号量成功\n");
//创建线程
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, handler1, NULL);
pthread_create(&tid2, NULL, handler2, NULL);
//回收线程
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
//销毁信号量
sem_destroy(&sem1);
sem_destroy(&sem2);
return 0;
}
小练习
创建俩线程,实现:将一个文档的内容打印到终端上。类似cat一个文件。
1)用标准IO实现;
2)一个线程读取,一个线程打印
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
sem_t sem_r, sem_w;
char buf[BUFSIZ] = "";
pthread_t tid_r, tid_w;
int flag = 0;
void* read_handler(void* arg)
{
FILE* fp = (FILE*)arg;
while(1)
{
sem_wait(&sem_r);
bzero(buf, sizeof(buf));
if(fgets(buf, BUFSIZ-1, fp) == NULL)
{
//读完5
//方式一
pthread_cancel(tid_w);
/*
//方式二
sem_post(&sem_w);
*/
/*
//方式三
flag = 1;
sem_post(&sem_w);
*/
break;
}
sem_post(&sem_w);
}
pthread_exit(NULL);
}
void* write_handler(void* arg)
{
while(1)
{
sem_wait(&sem_w);
/*
//方式二
if(strlen(buf) == 0)
{
break;
}
*/
/*
//方式三
if(flag)
{
break;
}
fputs(buf, stdout);
*/
sem_post(&sem_r);
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
if(argc < 2)
{
fprintf(stderr, "请传入文件名\n");
return -1;
}
//打开文件
FILE* fp = fopen(argv[1], "r");
if(NULL == fp)
{
perror("fopen");
return -1;
}
//初始化信号量
if(sem_init(&sem_r, 0, 1)<0)
{
fprintf(stderr, "line:%d ", __LINE__);
perror("sem_init");
return -1;
}
if(sem_init(&sem_w, 0, 0) < 0)
{
sem_destroy(&sem_r);
fprintf(stderr, "line:%d ", __LINE__);
perror("sem_init");
return -1;
}
pthread_create(&tid_r, NULL, read_handler, (void*)fp);
pthread_create(&tid_w, NULL, write_handler, NULL);
pthread_join(tid_r, NULL);
pthread_join(tid_w, NULL);
//销毁信号量
sem_destroy(&sem_r);
sem_destroy(&sem_w);
fclose(fp);
return 0;
}