UC编程之线程
线程--隶属于进程,是进程中的程序流。操作系统支持多进程,每个进程内部支持多线程。多线程并行(同时执行)代码。
进程--重量级的,每个进程都需要独立的内存空间。
线程--轻量级的,线程不拥有独立的内存资源,共享所在进程的内存资源,
但每个线程都拥有一个独立的栈区。
开发程序中,多线程使用的概率更高。
进程中可以有多个线程,但必须有一个主线程(main函数)。
由于CPU不可分,真正意义的并行其实不存在的,针对时间点的并行是不存在
的。人的感官都是针对时间段(很少)。主流的操作系统都是使用CPU时间片的
方式实现并行。每个CPU时间片都是极少的CPU运行时间。
多线程编程有一套成形的API,属于POSIX规范。
POSIX规范提供了一个头文件pthread.h 和一个共享库文件lib pthread.so。
因此在编写多线程代码时需要#include<pthread.h>,链接时需要加 -l pthread
或 -pthread
如何创建线程?
关于线程的大多数函数/成员变量都是以pthread_开头。
int pthread_create(pthread_t *id, const pthread_attr_t *attr,
void *(fa) (void *), void *p)
参数id用于存储新建线程的ID
attr用于传入新建线程的属性,一般0即可
fa是函数指针,线程执行的代码写成函数fa并在创建线程时传入
p是fa的参数,如果不需要0即可
注意:线程的错误处理不使用errno,而是直接返回。
当同一进程内部有多线程时,每个线程内部的代码顺序执行,而多线程之间的
代码乱序执行。
多线程之间 相互独立,但也相互影响。
主线程结束,进程随之结束;进程结束,进程中所有线程都结束。
pthread_join函数挂起当前线程直到所等待的线程结束。
线程的参数由 pthread_create的第4个参数传入,返回值由pthread_join的第二
个参数获取。
函数pthread_exit(void*)也可以退出线程并且也有返回值(参数void*就是线程的
返回值)
函数exit()退出的是进程(所有线程),pthread_exit()退出单个线程。
线程的运行有两种状态:
非分离状态--可以用函数pthread_join
分离状态 --可以用函数pthread_detach
两者的区别:
非分离状态的线程资源回收要到pthread_join函数结束以后;分离状态的线程资源马上回收。处于分离状态的线程无法join(join没效果)
经验:线程启动后,最好设置join或者detach
实例:
创建线程:
1 #include<stdio.h>
2 #include<pthread.h>
3
4 void* task(void* p){
5 int i;
6 for(i=0;i<100;i++){
7 printf("i=%d\n",i);
8 }
9 }
10
11
12 int main(){//主线程
13 pthread_t id;
14 pthread_create(&id,0,task,0);//创建线程
15 int i;
16 for(i=0;i<100;i++){
17 printf("main=%d\n",i);
18 }
19 sleep(1);
20 pthread_t id2 = pthread_self();//取当前线程
21 printf("id=%u,id2=%u\n",id);
22 return 0;
23 }
等待线程(pthread_join):
1 #include<stdio.h>
2 #include<pthread.h>
3
4 void* task(void* p){
5 int* pi = p;
6 printf("*pi=%d\n",*pi);
7 *pi =200;
8 }
9 int main(){//主线程
10 pthread_t id1;
11 int x =100;
12 pthread_create(&id1,0,task,&x);//创建线程
13 pthread_join(id1,0);//主线程等待id1的结束
14 printf("x=%d\n",x);
15 return 0;
16 }
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<pthread.h>
4
5 void* task(void* p){
6 printf("%d\n",(int)p);
7
8 }
9 void* task2(void* p){
10 sleep(1);
11 int* pi = p;
12 printf("*pi=%d\n",*pi);
13 }
14 int main(){
15 pthread_t id;
16 int x = 100;
17 pthread_create(&id,0,task,(void*)x);
18 pthread_join(id,0);
19 int* pi = malloc(4);
20 *pi=300;
21 pthread_create(&id,0,task2,pi);
22 free(pi);//free()释放掉了pi,使pi无效,传入task2将无效
23 pthread_join(id,0);
24 return 0;
25 }
线程的取消--线程可以被其他线程取消,有一套API(了解)
1 #include <stdio.h>
2 #include <pthread.h>
3 void* task1(void *p){//线程2取消线程1
4 //设置可以取消
5 pthread_setcancelstate(//换成DISABLE不能取消
6 PTHREAD_CANCEL_ENABLE,0);
7 //设置取消方式: 立即/下一个取消点
8 pthread_setcanceltype(//立即,DEFERRED下一个
9 PTHREAD_CANCEL_DEFERRED/*ASYNCHRONOUS*/,0);
10 while(1) {
11 printf("I am superman!\n");
12 usleep(1); } }
13 void* task2(void *p){
14 sleep(3); printf("开始取消线程1\n");
15 pthread_cancel(*(pthread_t*)p); }
16 int main(){
17 pthread_t id1,id2;
18 pthread_create(&id1,0,task1,0);
19 pthread_create(&id2,0,task2,&id1);
20 pthread_join(id1,0); pthread_join(id2,0);
21 return 0;
22 }
线程同步--因为多线程之间共享进程的资源,多个线程同时访问相同的资源时需要互相协调,以防止数据出现不一致、不完整的问题,线程之间的协调和通信叫线程同步。
线程同步有很多解决方案:
线程同步的思路:访问共享资源时,不能并行,而是串行(一个一个的访问)
pthread中,提供了互斥量(互斥锁)实现线程同步。
互斥量的使用步骤:
1 定义互斥量 pthread_mutex_t lock;
2 初始化互斥量,方法有二:
pthread_mutex_init(&lock,0);
或者在定义的时候用宏同时赋值:
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
3 加上互斥锁(给访问共享资源的代码加)
pthread_mutex_lock(&lock)
4 访问
5 解锁 pthread_mutex_unlock(&lock)
6 释放互斥锁的资源
pthread_mutex_destroy(&lock)
互斥量实例:
1 #include<stdio.h>
2 #include<pthread.h>
3
4 char* data[5];//存数据
5 int size = 0; //人数
6
7 pthread_mutex_t lock; //1 定义
8 //= PTHREAD_MUTEX_INITIALIZER;
9
10 void* task(void* p){
11 pthread_mutex_lock(&lock);//3 上锁
12 data[size] = (char*)p;
13 usleep(100000);
14 size++;
15 pthread_mutex_unlock(&lock);//4 解锁
16 }
17
18 int main(){
19 pthread_mutex_init(&lock,0);//2 初始化
20 data[size] = "zhangfei";
21 size++;
22 pthread_t id1,id2;
23 pthread_create(&id1,0,task,"guanyu");
24 pthread_create(&id2,0,task,"zhaoyun");
25 pthread_join(id1,0);
26 pthread_join(id2,0);
27 int i;
28 for(i=0;i<size;i++){
29 printf("%s\n",data[i]);
30 }
31 pthread_mutex_destroy(&lock);
32 return 0;
33 }
信号量-- 就是一个计数器,控制同时访问共享资源的线程/进程数量。
如果信号量为1,效果等同于互斥量。
信号量的使用,已经有了固定的API,步骤:
1 定义一个信号量(semaphore)
sem_t sem;
信号量不属于pthread.h
2 初始化信号量
sem_init(&sem,0,最大值);
第二个参数必须是0,0代表控制线程,其他值代表控制进程,Linux只支持线程。
第三个参数就是计数的初始值,为1信号量的作用等价于互斥量。
3 获取一个信号量(信号量减1)
sem_wait(&sem);
4 访问共享资源
5 释放一个信号量(信号量加1)
sem_post(&sem)
6 如果不再使用,可以删除信号量
sem_destroy(&sem)
信号量实现线程同步实例:
1 #include<stdio.h>
2 #include<pthread.h>
3 #include<semaphore.h>
4
5 char* data[5];//存数据
6 int size = 0; //人数
7
8 sem_t sem; //1 定义
9
10 void* task(void* p){
11 sem_wait(&sem);//3 上锁
12 data[size] = (char*)p;
13 usleep(100000);
14 size++;
15 sem_post(&sem);//4 解锁
16 }
17
18 int main(){
19 sem_init(&sem,0,1);//2 初始化
20 data[size] = "zhangfei";
21 size++;
22 pthread_t id1,id2;
23 pthread_create(&id1,0,task,"guanyu");
24 pthread_create(&id2,0,task,"zhaoyun");
25 pthread_join(id1,0);
26 pthread_join(id2,0);
27 int i;
28 for(i=0;i<size;i++){
29 printf("%s\n",data[i]);
30 }
31 sem_destroy(&sem);
32 return 0;
33 }
练习:
控制访问数据库的最大的并行线程数量
数据库最多运行10个线程同时访问,启动20个线程,看一下信号量控制效果
1 #include<stdio.h>
2 #include<pthread.h>
3 #include<semaphore.h>
4
5 sem_t sem;
6 void* task(void* p){
7 int i =(int)p;
8 printf("第%d个线程启动,申请连接\n",i);
9 sem_wait(&sem);
10 printf("第%d个线程申请成功\n",i);
11 sleep(10);
12 printf("第%d个线程完成连接\n",i);
13 sem_post(&sem);
14 }
15 int main(){
16 sem_init(&sem,0,10);//初始化
17 int i;
18 for(i = 0;i<20;i++){
19 pthread_t id;
20 pthread_create(&id,0,task,(void*)(i+1));
21 }
22 while(1);
23 // sem_destroy(&sem);
24 return 0;
25 }
死锁:线程之间互相锁定,导致所有线程都无法运行。多线程一定要避免锁死
避免死锁的经验:
顺序上锁,反向解锁,不要回调。
thread1:
lock(&mutex1);
...//1
lock(&mutex2);//3 thread1阻塞等待mutex2
...
unlock(&mutex2);
...
unlock(&mutex1);
thread2:
lock(&mutex2);//2
...//4
lock(&mutex1);//5 thread2阻塞等待mutex1
...
unlock(&mutex1);
...
unlock(&mutex2);
thread1运行到lock(&mutex2)时,如果thread2已经运行了lock(&mutex2)则thread1将阻塞等待thread2,thread2运行到lock(&mutex1);时,因为thread1已经运行了lock(&mutex1);thread2将阻塞等待thread1,结构thread1和thread2都将永远等待对方,无法结束,形成死锁