线程
1、理论和原理
1)主流的操作系统都是支持多进程的,每个进程的内部可以启动多线程完成代码的并行;每个线程的内部可以无限启动多线程。
2)线程是轻量级的,不拥有自己独立的内存空间,共享所在进程的相关资源(代码区/全局区/堆/文件状态/共享资源),线程拥有的只是一个独立的栈空间。
3)进程是重量级的,必须拥有自己独立的内存空间。
4)计算机执行代码必备CPU/内存,多线程的执行也是。
5)内存可分,CPU不可分。多线程可以并行,多线程的目的其实就是要多个线程并行执行一段代码。
6)0.1秒分成100个CUP时间片,每个1毫秒,这样当我们看到代码运行的结果时,每个线程都有运行,因此造成了并行的效果,但没有真正的并行。
7)多线程是利用CPU时间片实现的一种假的并行,但还是可以大幅提升cpu的利用效率,因此说线程是并行的
8)进程中可以有多个线程,但其中必须有一个是主线程,每个进程都必须有一个主线程,就是main函数。
9)线程内部是顺序执行代码,但多线程之间是乱序执行。线程之间互相影响,但不互相依赖。
2 、编码
1)UC中,线程开发需要头文件pthread.h和遵循POSIX规范。
2)对应的库文件 libpthread.so,gcc时需要带上库。
3)线程相关函数基本都是以pthread_开头。
4)创建线程的函数:
int pthread_create(pthread_t* threadid,属性指针,void* (*fun) (void *),void*p)
threadid 存储线程id
属性指针一般给NULL
fun 是函数指针,线程执行的代码写在函数中
p 是传给函数的参数,没有就给NULL
返回 0代表成功,失败会返回错误码
注:线程这里不使用errno和perror
5)主线程结束,导致进程结束,进程结束,导致所有线程的结束。
6)pthread_join 函数 可以让一个线程等待另外一个线程的结束,并取得该线程的返回值。
7)结束线程用return或pthread_exit,可能需要作类型转换,否则警告。返回值可以用pthread_join取得。
8)线程有分离状态和非分离状态,分离状态的线程资源回收无需等待其他线程,因此分离状态的线程join无效。pthread_detach 可以把线程变成分离状态。
9)线程可以被其他线程取消,pthread_cancel可以取消线程的运行,需要用pthread_setcancelstate设置是否支持取消,用pthread_setcanceltype设置取消的类型(立即还是延后).
3、线程(thread) - 做应用,有网络必有线程。
1)多线程之间互相独立,又互相影响。
2)多线程可以大幅提升代码的效率。
3)程序的运行必须拥有CPU和内存,内存可分, CPU不可分,如何实现并行。大多数的操作系统都是采用CPU时间片实现CPU的在多线程之间的轮换。CPU时间片是极短的一段CPU的执行时间,拥有CPU时间片的线程有机会运行。
比如:人的感官是需要时间的,比如视觉0.1秒。就是100毫秒。假定CPU时间片是1毫秒,有4个线程。每个线程先分一个时间片,也就是1毫秒的CPU运行时间。每个线程只能运行1毫秒,时间片的运行时间到了以后就只能看其他线程运行,直到所有线程的时间片都运行完毕,再重新分配。
4)针对时间点的并行是不存在的,针对时间段的并行就是我们通常说的代码并行。
5)每个进程都有一个主线程,就是main()函数,主线程结束,进程也结束,同时导致所有线程都结束。
线程的编程:
Unix/Linux的线程相关函数都在pthread.h中,代码都在libpthread.so中。线程相关的函数/结构都以pthread_ 开头。比如创建线程函数:pthread_create();
int pthread_create(pthread_t* id,pthread_attr_t* attr, void* (*fa)(void*),void* arg)
pthread_create()是一个四针函数,参数id就是用于存储线程ID的;attr是线程的属性,一般给0即可(默认属性); fa是一个函数指针,写线程执行的代码;arg是传给fa的参数。fa+arg指定了线程要执行的代码。
返回值: 成功返回0,失败返回错误码,想看错误信息需要用strerror()做转换。
6)每个线程启动以后,只能执行一个函数,主线程执行的是main(),其他线程执行自定义的一个函数。这个函数以并行的方式运行。
7)线程之间的代码乱序执行,每个线程的内部代码都是顺序执行。每个线程都会返回自己的错误码,而不是使用errno。
8)pthread_join()函数可以让一个线程等待另外一个线程的结束,并取得线程的返回值。
9)如果在线程a中调用了pthread_join(b,0),线程a就会等待线程b的结束,等线程b结束以后a才能继续运行。
10)线程传参时,一定要注意保证地址的有效性,尤其是堆内存。支持直接传递int。
关于函数的返回:
1 能返回局部变量,但不能返回指向局部变量的指针。
2 static的局部变量的地址可以返回(全局区)。
3 数组理论上可以做返回值,但返回值类型不能写数组。最好用指针。int[] get() 错.
void fa(int* pi){ *pi = 200;}
int main(){
int x;
fa(&x); -> pi = &x ->*pi = x = 200;
}
void fa(int** pi){ *pi = 地址1;}
int main(){
int* px;
fa(&px); ->pi = &px ->*pi = px = 地址1;
}
4、 线程的状态:
线程应该处于以下两种状态:
1 分离状态
就是线程一旦结束,不用管其他线程,直接回收资源。函数pthread_detach()设置线程分离状态。
2 join状态
如果线程用pthread_join(),就处于join()状态,就是线程结束时暂不回收资源,到pthread_join()函数结束时再回收资源。
注:没有分离也没有join()的线程资源回收是没有保障的。
分离状态的线程再调用pthread_join()没有效果
5、线程的退出
正常退出:
在线程的函数中执行了return语句。
执行了pthread_exit(void*)函数
非正常退出:
自身出现错误
被其他线程终止/取消
exit()和pthread_exit(void*)的区别?
exit() 是结束进程,所有线程全结束
pthread_exit()是结束线程,其他线程继续运行
参数void* 和 return 一样,都是 用于返回值。
取消线程的函数: pthread_cancel()
多线程之间是共享进程的资源,因此有可能出现共享数据的冲突,解决方案就是把并行访问改为串行访问,这种技术叫线程同步。线程同步的技术包括: 互斥量、信号量、条件变量。
互斥量又叫互斥锁,是线程在设计时官方的同步技术,编程步骤如下:
1 声明互斥量
pthread_mutex_t lock; //变量名不一定叫lock
2 初始化互斥量
pthread_mutex_init(&lock); 或在声明的同时赋值: pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
3 加锁/上锁 pthread_mutex_lock(&lock);
4 执行共享数据的访问代码
5 解锁 pthread_mutex_unlock(&lock);
6 释放锁的资源
pthread_mutex_destroy(&lock);
信号量是一个计数器,用于控制访问共享资源的最大的并行 进程/线程的数量。
信号量的工作原理:先设置最大值最初始计数,每上来一个计数减1,每退出一个就加1,到0就不允许新进程/线程访问,除非计数又回到大于0。
信号量不属于线程的范围,不在pthread.h中,只是一个线程计数辅助。头文件 semaphore.h
信号量如果初始计数为1,效果等同于互斥量。
信号量的编程步骤:
1 声明信号量 sem_t sem;
2 初始化信号量的原始计数 sem_init()
sem_init(&sem,0,count)
第一个参数就是信号量的地址
第二个参数必须是0,0代表线程的计数,非0代表进程的计数(Linux系统没有提供进程计数功能)。
第三个参数就是 计数的初始值(最大计数)。
3 计数减1 sem_wait(&sem);
4 正常使用
5 计数加1 sem_post(&sem);
6 释放信号量资源 sem_destroy(&sem);
使用线程同步技术,小心避免死锁。
pthread_mutex_t lock1,lock2;
线程a:
lock(&lock1);
...
lock(&lock2); //等待线程b unlock(&lock2)
...
unlock(&lock2);
unlock(&lock1);
线程b:
lock(&lock2);
...
lock(&lock1);//等待线程a unlock(&lock1)
...
unlock(&lock1);
unlock(&lock2);
结果就是看起来都问题,但执行 a和b互相锁定,死锁。
实例:
(1)
#include <stdio.h>
#include <pthread.h>
#include <string.h>
void * task1(void *p){
int i;
for(i=0;i<10;i++) {
printf("%d\n",i);
usleep(100000);
}
}
void * task2(void *p){
char c;
for(c='a';c<'k';c++) {
printf("%c\n",c);
usleep(100000);
}
}
int main(){
pthread_t id1,id2;
int res = pthread_create(&id1,0,task1,NULL);
if(res) printf("create:%s\n",strerror(res));
res = pthread_create(&id2,0,task2,NULL);
if(res) printf("create:%s\n",strerror(res));
sleep(2);
return 0;
}
(2)
#include <stdio.h>
#include <pthread.h>
#include <string.h>
char* get(){
char * str = "abcd";
//char str[] = "abcd";// 栈区复制一份
return str;
}
void* task(void *p){
strcpy(p,"12345");
return "WORLD";
}
void* task2(void *p){
int *pi = p;
int i;
static int sum = 0;
for(i=1;i<=*pi;i++){
sum = sum+i;
}
return ∑
}
int main(){
//char *s = get(); printf("s=%s\n",s);
pthread_t id1;
char str[] = "hello";
pthread_create(&id1,NULL,task,str);
char *res;//join+一级指针变量可以取返回
pthread_join(id1,(void**)&res);
printf("str=%s,return=%s\n",str,res);
int num = 10;
pthread_create(&id1,NULL,task2,&num);
int *pi;
pthread_join(id1,(void**)&pi);
printf("sum=%d\n",*pi);
}
(3)
#include <stdio.h>
#include <pthread.h>
void* task(void *p){
//设置是否开启取消
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,0);
//pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,0);
pthread_setcanceltype(//立即退出
PTHREAD_CANCEL_ASYNCHRONOUS,0);
//pthread_setcanceltype(//下一个取消点退出
//PTHREAD_CANCEL_DEFERED,0);
while(1) { printf("-----------\n"); usleep(1); }}
void* task2(void *p){
usleep(100000); printf("开始取消线程1....\n");
pthread_cancel(*(pthread_t*)p); }
int main(){
pthread_t id1,id2;
pthread_create(&id1,0,task,0);
pthread_create(&id2,0,task2,&id1);
pthread_join(id1,0); pthread_join(id2,0);
}