一.多线程
1.了解多线程
解决多任务实现。
历史上Unix服务器不支持多线程
Unix/Linux上实现多线程有两种方式:
内核支持多线程
使用进程的编程技巧封装进程实现多线程:轻量级多线程
多线程的库:
libpthread.so -lpthread
pthread.h
2.创建多线程
2.1.代码?
回调函数
2.2.线程ID?
pthread_t
2.3.运行线程?
pthread_create
int pthread_create(
pthread_t *th,//返回进程ID
const pthread_attr_t *attr,//线程属性,为NULL/0,使用进程的默认属性
void*(*run)(void*),//线程代码
void *data);//传递线程代码的数据
看一个小例子:
#include <stdio.h> #include <pthread.h> void *run(void *data) { printf("我是线程!\n"); } main() { pthread_t tid; pthread_create(&tid, 0, run, 0); }
运行程序,发现并没有输出。为什么呢?请看结论:
结论:
1.程序结束所有子线程就结束
解决办法:等待子线程结束
sleep/pause
int pthread_join(
pthread_t tid,//等待子线程结束
void **re);//子线程结束的返回值
#include <stdio.h> #include <pthread.h> void *run(void *data) { printf("我是线程!\n"); } main() { pthread_t tid; pthread_create(&tid, 0, run, 0); //sleep(1); pthread_join(tid, (void **)0); }
2.创建子线程后,主线程继续完成系统分配时间片。
3.子线程结束就是线程函数返回。
4.子线程与主线程有同等优先级别.
作业:
写一个程序创建两个子线程
#include <stdio.h> #include <pthread.h> void *run(void *data) { printf("我是线程!\n"); } void *run2(void *data) { printf("我是线程2!\n"); } main() { pthread_t tid; pthread_t tid2; pthread_create(&tid, 0, run, 0); pthread_create(&tid2, 0, run2, 0); //sleep(1); pthread_join(tid, (void **)0); pthread_join(tid2, (void**)0); }
3.线程的基本控制
线程的状态:
ready->runny->deady
|
sleep/pause
结束线程?
内部自动结束:(建议)
return 返回值;(在线程函数中使用)
void pthread_exit(void*);(在任何线程代码中使用)
#include <stdio.h> #include <unistd.h> #include <pthread.h> #include <sched.h> void call() //用一个函数来退出线程的时候,就可以看到return和exit的区别 { pthread_exit("Kill"); //可以让线程结束 return ; //只能让函数退出,并不能让线程退出 } void* run(void* data) { while(1) { printf("我是线程!%s\n"); sched_yield(); //放弃当前时间片。等待下一次执行 //return "hello"; pthread_exit("world"); } } main() { pthread_t tid; char *re; pthread_create(&tid,0,run,0); pthread_join(tid,(void**)&re); //re接收返回值 printf("%s\n",re); }
外部结束一个线程.
pthread_cancel(pthread_t);
小应用: 用多线程来写7为随机数和当前时间的显示
#include <curses.h> #include <pthread.h> #include <time.h> #include <math.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> //全局变量两个窗体 WINDOW *wtime,*wnumb; pthread_t thnumb,thtime; pthread_mutex_t m; //线程1:随机数 void*runnumb(void *d) { int num; while(1) { //循环产生7位随机数 num=rand()%10000000; pthread_mutex_lock(&m); //显示 mvwprintw(wnumb,1,2,"%07d",num); //刷新 refresh(); wrefresh(wnumb); pthread_mutex_unlock(&m); usleep(1); } return 0; } //线程2:时间 void*runtime(void*d) { time_t tt; struct tm *t; while(1) { //循环取时间 tt=time(0); t=localtime(&tt); pthread_mutex_lock(&m); //显示 mvwprintw(wtime,1,1,"%02d:%02d:%02d", t->tm_hour,t->tm_min,t->tm_sec); //刷新 refresh(); wrefresh(wtime); pthread_mutex_unlock(&m); usleep(1); } } main() { //初始化curses initscr(); curs_set(0); noecho(); keypad(stdscr,TRUE); wnumb=derwin(stdscr,3,11, (LINES-3)/2,(COLS-11)/2); wtime=derwin(stdscr,3,10,0,COLS-10); box(wnumb,0,0); box(wtime,0,0); refresh(); wrefresh(wnumb); wrefresh(wtime); pthread_mutex_init(&m,0);//2 //创建线程1 pthread_create(&thnumb,0,runnumb,0); //创建线程2 pthread_create(&thtime,0,runtime,0); //等待按键 //结束 getch(); pthread_mutex_destroy(&m);//3 delwin(wnumb); delwin(wtime); endwin(); }
4.多线程的问题
数据脏
#include <stdio.h> #include <pthread.h> int a=0,b=0; void display() { a++; b++; if(a!=b) { printf("%d!=%d\n",a,b); a=b=0; } } void *r1() { while(1) { display(); } } void *r2() { while(1) { display(); } } main() { pthread_t t1,t2; pthread_create(&t1,0,r1,0); pthread_create(&t2,0,r2,0); pthread_join(t1,(void**)0); pthread_join(t2,(void**)0); }
看上去好像程序没有问题,但是会输出很多很多不相等的a,b数据,甚至很多a,b相等的也被输出来了。这就是两个线程之前共用数据时的问题。
5.多线程问题的解决
互斥锁/互斥量 mutex
1.定义互斥量pthread_mutex_t
2.初始化互斥量 默认是1 pthread_mutex_init
3.互斥量操作 置0 phtread_mutex_lock
判定互斥量0:阻塞
1:置0,返回
置1 pthread_mutex_unlock
置1返回
强烈要求成对使用
4.释放互斥量pthread_mutex_destroy
#include <stdio.h> #include <pthread.h> //1.定义互斥量 pthread_mutex_t m; int a=0,b=0; void display() { //3.操作互斥量 pthread_mutex_lock(&m); a++; b++; if(a!=b) { printf("%d!=%d\n",a,b); a=b=0; } pthread_mutex_unlock(&m); } void *r1() { while(1) { display(); } } void *r2() { while(1) { display(); } } main() { pthread_t t1,t2; //2. 初始化互斥量 pthread_mutex_init(&m,0); pthread_create(&t1,0,r1,0); pthread_create(&t2,0,r2,0); pthread_join(t1,(void**)0); pthread_join(t2,(void**)0); //4. 释放互斥量 pthread_mutex_destroy(&m); }
结论:
互斥量保证锁定的代码一个线程执行,
但不能保证必需执行完!
5.在lock与unlock之间,调用pthread_exit?
或者在线程外部调用pthread_cancel?
其他线程被永久死锁.
6.pthread_cleanup_push {
pthread_cleanup_pop }
这对函数作用类似于atexit
注意:
这不是函数,而是宏.
必须成对使用
#include <stdio.h> #include <stdlib.h> #include <pthread.h> pthread_mutex_t m; void handle(void *d) { printf("退出后的调用!\n"); pthread_mutex_unlock(&m); } void* runodd(void *d) { int i=0; for(i=1;;i+=2) { pthread_cleanup_push(handle,0); //pthread_exit()和pthread_cancle()以及pthread_cleanup_pop(1)参数为1时会触发 pthread_mutex_lock(&m); printf("%d\n",i); pthread_cleanup_pop(1); //参数为1就会弹出handle并执行函数,如果参数为0,则只弹出,不执行函数。 } } void* runeven(void *d) { int i=0; for(i=0;;i+=2) { pthread_cleanup_push(handle,0); pthread_mutex_lock(&m); printf("%d\n",i); pthread_cleanup_pop(1); } } main() { pthread_t todd,teven; pthread_mutex_init(&m,0); pthread_create(&todd,0,runodd,0); pthread_create(&teven,0,runeven,0); sleep(5); pthread_cancel(todd); pthread_join(todd,(void**)0); pthread_join(teven,(void**)0); pthread_mutex_destroy(&m); }
6.多线程的应用
二.多线程同步
互斥量/信号/条件量/信号量/读写锁
1.sleep与信号
pthread_kill向指定线程发送信号
signal注册的是进程的信号处理函数.
pthread_kill+sigwait控制进程
1.1.定义信号集合
1.2.初始化信号集合
1.3.等待信号
1.4.其他线程发送信号
1.5.清空信号集合
#include <stdio.h> #include <pthread.h> #include <unistd.h> #include <signal.h> pthread_t t1,t2; sigset_t sigs; void handle(int s) { printf("信号!\n"); } void*r1(void*d) { int s; while(1) { printf("线程--1\n"); sigwait(&sigs,&s); printf("接收到信号:%d!\n",s); } } void*r2(void*d) { while(1) { printf("线程----2\n"); sleep(2); pthread_kill(t1,SIGUSR1); } } main() { sigemptyset(&sigs); //sigaddset(&sigs,SIGUSR1); sigfillset(&sigs); //signal(SIGUSR1,handle); pthread_create(&t1,0,r1,0); pthread_create(&t2,0,r2,0); pthread_join(t1,(void**)0); pthread_join(t2,(void**)0); }
案例:
sigwait实际处理了信号
如果进程没有处理信号,目标线程也没有sigwait
,则进程会接收信号进行默认处理
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <sched.h> sigset_t sigs; pthread_t todd,teven; void* runodd(void *d) { int i=0; int s; for(i=0;;i+=2) { printf("%d\n",i); sigwait(&sigs,&s); } } void* runeven(void *d) { int i=0; int s; for(i=1;;i+=2) { printf("%d\n",i); sleep(1); pthread_kill(todd,34); } } main() { sigemptyset(&sigs); sigaddset(&sigs,34); pthread_create(&todd,0,runodd,0); pthread_create(&teven,0,runeven,0); pthread_join(todd,(void**)0); pthread_join(teven,(void**)0); }
2.条件量
信号量类似
2.1.定义条件量
2.2.初始化条件量
2.3.等待条件量
2.4.其他线程修改条件量
2.5.释放条件量
案例:
创建两个线程.
一个线程等待信号
一个线程每隔1秒发送信号
1.使用pause+pthread_kill
#include <stdio.h> #include <pthread.h> #include <signal.h> pthread_t t1,t2; void handle(int s) //什么也不干。但是可以防止t1没有sigwait()处理信号,程序异常退出。 { } void *r1(void* d) { while(1) { pause(); //pause受信号影响,不起作用了。所以程序一直循环打印。 printf("活动!\n"); } } void *r2(void* d) { while(1) { sleep(1); pthread_kill(t1,34); } } main() { signal(34,handle); pthread_create(&t1,0,r1,0); pthread_create(&t2,0,r2,0); pthread_join(t1,(void**)0); pthread_join(t2,(void**)0); }
上面这种方式看起来不太好,有一个怪怪的函数,什么也没干。下面用sigwait来处理:
#include <stdio.h> #include <pthread.h> #include <signal.h> pthread_t t1,t2; sigset_t sigs; void *r1(void* d) { int s; while(1) { sigwait(&sigs,&s); printf("活动!\n"); } } void *r2(void* d) { while(1) { sleep(1); //sigwait必须先于pthread_kill执行, //才能正确接收到信号,不然也会异常退出。 //所以睡眠一会儿,保证每次kill之前都已经wait了。 pthread_kill(t1,34); } } main() { sigemptyset(&sigs); sigaddset(&sigs,34); pthread_create(&t1,0,r1,0); pthread_create(&t2,0,r2,0); pthread_join(t1,(void**)0); pthread_join(t2,(void**)0); }
上面这种方法的缺陷如sleep()后面的注释。
看下面条件量的处理:
条件量则没有上面的那个问题,不用等wait先执行。
#include <stdio.h> #include <pthread.h> #include <signal.h> pthread_t t1,t2; pthread_cond_t cond;//1. pthread_mutex_t m; void *r1(void* d) { int s; while(1) { pthread_cond_wait(&cond,&m); //在非互斥的线程里面,m参数形同虚设。 //如果在互斥的线程里,m可以解锁,让另一个线程执行, //并发出信号让wait接收,防止死锁。 printf("活动!\n"); } } void *r2(void* d) { while(1) { pthread_cond_signal(&cond); //条件量不累计,发多个也相当于一个的效果。 pthread_cond_signal(&cond); pthread_cond_signal(&cond); sleep(10); } } main() { pthread_mutex_init(&m,0); pthread_cond_init(&cond,0);//2 pthread_create(&t1,0,r1,0); pthread_create(&t2,0,r2,0); pthread_join(t1,(void**)0); pthread_join(t2,(void**)0); pthread_cond_destroy(&cond); pthread_mutex_destroy(&m); }
pthread_cond_*** 与sigwait都是进程同步控制
pthread_cond_***稳定
pthread_cond_***在环境下不会死锁.
课堂练习:
使用条件量与互斥构造死锁程序.
#include <stdio.h> #include <pthread.h> pthread_t t1,t2; pthread_mutex_t m1,m2; pthread_cond_t c; void* r1(void*d) { while(1) { pthread_mutex_lock(&m1); printf("我是等待!\n"); pthread_cond_wait(&c,&m1); //如果这里是m2,则不能解r2的锁,造成死锁 pthread_mutex_unlock(&m1); } } void* r2(void *d) { while(1) { pthread_mutex_lock(&m1); printf("我是让你不等待!\n"); pthread_cond_signal(&c); pthread_mutex_unlock(&m1); } } main() { pthread_cond_init(&c,0); pthread_mutex_init(&m1,0); pthread_mutex_init(&m2,0); pthread_create(&t1,0,r1,0); pthread_create(&t2,0,r2,0); pthread_join(t1,0); pthread_join(t2,0); pthread_mutex_destroy(&m2); pthread_mutex_destroy(&m1); pthread_cond_destroy(&c); }
作业:
1.写一个程序:
两个线程写数据到文件.
数据格式:日期时间,线程ID\n
要求:
要求使用互斥, 保证数据正确.
体会使用互斥和不使用互斥的异同.
2.使用curses写一个多线程程序
开启26个线程.每个线程控制一个字母在屏幕上掉落
建议每隔字母的高度随机.
#include <pthread.h> #include <curses.h> #include <math.h> struct AChar { int x; int y; int speed; char a; }; int stop=1; pthread_t t[26]; pthread_t tid; pthread_mutex_t m; struct AChar a[26]; void *run(void *d) { int id; static idx=-1; idx++; id=idx; while(stop) { pthread_mutex_lock(&m); //改变对象的y坐标 a[id].y+=a[id].speed; if(a[id].y>=LINES) { a[id].y=rand()%(LINES/4); } pthread_mutex_unlock(&m); sched_yield(); usleep(100000); } } void * update(void *d) { int i=0; while(stop) { erase(); //绘制屏幕上 for(i=0;i<26;i++) { mvaddch(a[i].y,a[i].x,a[i].a); } //刷屏 refresh(); usleep(10000); } } main() { int i; initscr(); curs_set(0); noecho(); keypad(stdscr,TRUE); for(i=0;i<26;i++) { a[i].x=rand()%COLS; a[i].y=rand()%(LINES/4); a[i].speed=1+rand()%3; a[i].a=65+rand()%26; } pthread_mutex_init(&m,0); pthread_create(&tid,0,update,0); for(i=0;i<26;i++) { //随机产生字母与位置 pthread_create(&t[i],0,run,0); } getch(); stop=0; for(i=0;i<26;i++) { //随机产生字母与位置 pthread_join(t[i],(void**)0); } pthread_join(tid,(void**)0); pthread_mutex_destroy(&m); endwin(); }
3.写一个程序:创建两个线程
一个线程负责找素数.
另外一个线程把素数保存到文件
要求:
找到以后,通知另外一个线程保存,停止招素数
线程保存好以后通知素数查找线程继续查找.
目的:
互斥与信号/条件量作用是不同.