第十一章:线程
Posted lioker
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第十一章:线程相关的知识,希望对你有一定的参考价值。
多进程在代码中并不多见,因为它有很大的局限性,如分配大量资源、进程的PID个数有限等。现在更多地是使用多线程实现代码并发。
一、线程的概念
线程是一种轻量级的代码并发技术,对资源的要求较小。线程隶属于某个进程,进程内部可以使用多线程,线程内部也可以使用多线程。
线程共享进程的资源,不需要太多额外的资源,每个线程只需要额外分配一个栈区即可。
在使用多线程之后,在程序设计时就可以把进程设计成在某一时刻不止做一件事,每个线程处理各自的任务。
我们之前写的代码都是单线程的,也就是main()函数线程,又称主线程。主线程一旦结束,进程也就随之结束,所有线程也随之结束。
二、线程标识
和每个进程有一个进程PID一样,每个线程也有一个线程ID。进程ID不可重复,但是线程ID属于某个进程,只要在此进程中的线程ID不重复即可。
获取自身线程ID使用函数为pthread_self(),其函数定义如下:
#include <pthread.h> pthread_t pthread_self(void); Compile and link with -pthread.
函数返回值类型是pthread_t,与进程的pid_t不同,pthread_t实现可能不是整数类型(操作系统不同,类型不同)。因此我们需要使用pthread_equal()函数来对两个线程ID进行比较,其函数定义如下:
#include <pthread.h> int pthread_equal(pthread_t t1, pthread_t t2); Compile and link with -pthread.
该函数若t1和t2线程ID相等,返回非0数值;否则,返回0。
Linux和Unix都在POSIX规范中对线程有了定义,主要是一个头文件pthread.h + 一个共享库libpthread.so。所有线程相关的函数、结构体和类型一般以pthread_开头,比如接下来要讲解的线程创建函数pthread_create()。
由于使用共享库,在编译链接时,需要加上-pthread或-lpthread
三、线程创建
新增的线程可以通过pthread_create()函数创建。其函数声明如下:
#include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
函数参数以及返回值:
thread:返回的线程ID
attr:线程的属性,一般给0即可
start_rountine:线程执行函数指针
arg:线程执行函数的函数参数
返回值:成功返回0;失败返回错误码,需要使用strerror()转换为错误信息
示例代码如下:
1 #include <stdio.h> 2 #include <pthread.h> 3 #include <unistd.h> 4 5 #define PI 3.141592 6 7 void *task(void *p) 8 { 9 int *r = (int *)p; 10 printf("半径为%d圆面积为:%lf\\n", *r, PI*(*r)*(*r)); 11 } 12 13 int main() 14 { 15 pthread_t id; 16 17 int x = 10; 18 pthread_create(&id, 0, &task, &x); 19 sleep(1); // 防止主线程先结束 20 21 return 0; 22 }
四、线程终止
如果进程中任意线程调用了exit()、_Exit()或_exit(),那么整个进程就会终止。
因此,线程需要有自己的终止方式:
1. 线程的函数执行了return或exit()。
2. 调用线程终止函数pthread_exit(void *),返回值做为终止参数
3. 线程被其它线程终止
pthread_exit()函数声明如下:
#include <pthread.h> void pthread_exit(void *retval);
retval参数与传给pthread_create()参数类似。进程中的其他线程可以通过pthread_join()函数访问到这个指针,其函数声明如下:
#include <pthread.h> int pthread_join(pthread_t thread, void **retval);
函数参数以及返回值:
thread:指定的线程ID
retval:pthread_exit()函数的返回值
返回值:成功返回0;失败返回错误码,需要使用strerror()转换为错误信息
pthread_join()函数会导致调用线程一直阻塞,直到指定的线程调用pthread_exit()、从线程中返回或被取消。
对于取消线程,我们可以使用pthread_cancel()函数,其函数声明如下:
#include <pthread.h>
int pthread_cancel(pthread_t thread);
示例代码如下:
1 #include <stdio.h> 2 #include <pthread.h> 3 #include <unistd.h> 4 5 void *task(void *p) 6 { 7 int *r = (int *)p; 8 int area = 0; // 只有int和void *可以互转 9 10 area = (*r) * (*r); 11 12 return (void *)area; // 不能返回局部变量的地址 13 } 14 15 int main() 16 { 17 pthread_t id; 18 19 int x = 10; 20 pthread_create(&id, 0, &task, &x); 21 int area; 22 pthread_join(id, (void**)&area); // 取地址即可 23 printf("长度为%d正方形面积为:%d\\n", x, area); 24 25 return 0; 26 }
在默认情况下,线程的终止状态会保存直到对该线程调用pthread_join()。如果线程已经被分离,线程的底层存储资源可以在线程终止时被立即收回。在线程被分离后,就不能用pthread_join()函数等待它的终止状态,因为对分离状态的线程调用pthread_join()会产生定义行为。可以调用pthread_detach()分离线程。
#include <pthread.h>
int pthread_detach(pthread_t thread);
示例代码如下:
1 #include <stdio.h> 2 #include <pthread.h> 3 #include <unistd.h> 4 5 void *task(void *p) 6 { 7 int i; 8 for(i = 0; i < 30; i++) 9 { 10 printf("task:%d\\n", i); 11 usleep(10000); 12 } 13 } 14 15 int main() 16 { 17 pthread_t id; 18 pthread_create(&id, 0, task, 0); 19 pthread_detach(id); 20 // pthread_join(id, 0); // 没有阻塞等待效果 21 int i; 22 for(i = 0; i < 30; i++) 23 { 24 printf("main:%d\\n", i); 25 usleep(10000); 26 } 27 28 return 0; 29 }
执行此代码,可以发现主线程和task线程同时执行,而不是主线程阻塞等待task线程执行完成。
五、线程同步
使用线程并发并发就会导致一个问题:假设线程A对一个文件写入3000个字符“a”,而另一个线程B对这个文件写入3000个“b”,第三个线程C读取这个文件,会导致读取数据不一定是什么。
因为可能在一段时间内先执行了A;当A执行到一半CPU切换到执行B了,这时就会导致数据混乱。
为了保证数据一致性,我们需要保证线程同步。也就是让线程在同一时间只允许一个线程访问该变量。
一般采用的方式有原子操作、自旋锁、信号量和互斥体。具体细节可查看:五、并发控制
线程对应的函数是以上函数的变体(一般是在以上函数前面加上pthread_,如自旋锁初始化函数pthread_spin_lock())。
读写锁:
读写锁与互斥量类似,不过读写锁允许更高的并行性。
其系列函数定义如下:
#include <pthread.h> /* 初始化读写锁 */ int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); /* 销毁读写锁 */ int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); /* 读锁 */ int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); /* 写锁 */ int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); /* 解锁 */ int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
条件变量:
条件变量使我们睡眠等待某种条件出现,然后线程才能继续执行,与信号(signal)类似。它必须与互斥量配合使用。
其系列函数定义如下:
#include <pthread.h> /* 初始化条件变量 */ int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); /* 注销条件变量 */ int pthread_cond_destroy(pthread_cond_t *cond); /* 唤醒条件变量 */ int pthread_cond_broadcast(pthread_cond_t *cond); int pthread_cond_signal(pthread_cond_t *cond); /* 等待条件变量 */ int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime); int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
自旋锁:
其系列函数定义如下:
#include <pthread.h> /* 自旋锁初始化 */ int pthread_spin_init(pthread_spinlock_t *lock, int pshared); /* 自旋锁注销 */ int pthread_spin_destroy(pthread_spinlock_t *lock); /* 上锁/解锁 */ int pthread_spin_lock(pthread_spinlock_t *lock); int pthread_spin_trylock(pthread_spinlock_t *lock); int pthread_spin_unlock(pthread_spinlock_t *lock);
互斥量:
其系列函数定义如下:
#include <pthread.h> /* 初始化互斥量 */ int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr); /* 销毁互斥量 */ int pthread_mutex_destroy(pthread_mutex_t *mutex); /* 上锁/解锁 */ int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock( pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex);
互斥量的示例代码如下:
1 #include <stdio.h> 2 #include <pthread.h> 3 #include <unistd.h> 4 5 char *name[5]; 6 int index; 7 8 pthread_mutex_t lock; // 1 声明 9 10 void *task(void *p) 11 { 12 pthread_mutex_lock(&lock); // 3 上锁 13 name[index] = (char*)p; 14 usleep(10000); 15 index++; 16 pthread_mutex_unlock(&lock); // 5 解锁 17 } 18 19 int main() 20 { 21 name[index] = "nihao"; 22 index++; 23 pthread_mutex_init(&lock, 0); // 2 初始化 24 pthread_t id1, id2; 25 pthread_create(&id1, 0, task, "hello"); 26 pthread_create(&id2, 0, task, "world"); // 两个进程运行,id1刚执行,id2也开始执行,暂停,id2覆盖id1 27 pthread_join(id1, 0); 28 pthread_join(id2, 0); 29 pthread_mutex_destroy(&lock); // 6 销毁 30 int i; 31 for(i = 0; i < index; i++) 32 printf("%s\\n", name[i]); // 空指针 33 34 return 0; 35 }
以上是关于第十一章:线程的主要内容,如果未能解决你的问题,请参考以下文章