目录
一,线程概念
二,Linux进程与线程
三,Linux线程控制
创建线程
线程终止
线程等待
线程分离
linux线程互斥
一,线程概念
在一程序内,一个执行路线称为线程thread,即线程是一个进程内部的控制序列;
线程优点
线程缺点
线程异常
线程用途
二,Linux进程与线程
进程的多个线程共享同一地址空间,因此Text Segment、Data Segment都是共享的;如定义一个函数,在各个线程中都可调用,如定义一个全局变量,在各线程都可访问到,除此之外,各线程还共享以下进程资源和环境:
三,Linux线程控制
POSIX线程库
线程创建
//创建新线程 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);
- thread,返回线程的ID;
- attr,设置线程的属性,如为NULL表示使用默认属性;
- start_routine,函数地址,线程启动后执行的函数;
- arg,传给线程启动函数的参数;
返回值
- 成功返回0,失败返回错误码;
#include
#include
#include
#include
#include
void* rout(void* arg){
for( ; ; ){
printf("I am thread1\n");
sleep(1);
}
}
int main(){
pthread_t tid;
int ret = pthread_create(&tid, NULL, rout, NULL);
if(ret != 0){
fprintf(stderr, "pthread_create: %s\n", strerror(ret));
exit(EXIT_FAILURE);
}
for( ; ; ){
printf("I am main thread\n");
sleep(1);
}
}
[wz@192 Desktop]$ gcc -o test test.c -lpthread
[wz@192 Desktop]$ ldd test
linux-vdso.so.1 => (0x00007ffce0bb2000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f9ab6dd8000)
libc.so.6 => /lib64/libc.so.6 (0x00007f9ab6a0a000)
/lib64/ld-linux-x86-64.so.2 (0x00007f9ab6ff4000)
[wz@192 Desktop]$ ./test
I am main thread
I am thread1
I am main thread
I am thread1
I am main thread
I am thread1
//一个进程,两个线程(轻量级进程)
[wz@192 ~]$ ps axj | head -1 && ps axj | grep test
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
2976 53194 53194 2976 pts/0 53194 Sl+ 1000 0:00 ./test
3351 53324 53323 3351 pts/1 53323 S+ 1000 0:00 grep --color=auto test
[wz@192 ~]$ ps -aL | head -1 && ps -aL | grep test
PID LWP TTY TIME CMD
53194 53194 pts/0 00:00:00 test
53194 53195 pts/0 00:00:00 test
线程ID及进程地址空间布局
pthread_t类型是什么,取决于实现;对于Linux目前实现的NPTL,pthread_t类型的线程ID,本质上是一个进程地址空间的一个地址;
- Linux没有真正意义上的线程,是用进程模拟的(轻量级进程);
- Linux本身不会直接提供类似线程创建、终止、等待、分离等相关system call接口,但会提供创建轻量级进程的接口vfork;
- 但用户需要所谓的线程创建、终止、等待、分离等相关接口,所以系统基于轻量级进程接口模拟封装了用户原生线程库pthread;
- 进程由PCB管理的,用户层也需进行用户级线程管理(由用户空间维护);
- 用户层线程ID,本质是一个地址(共享区,pthread库中某个起始位置);
线程终止
如需终止某个线程而不是整个进程,有三种方法:
void pthread_exit(void* value_ptr);
- vaule_ptr,不要指向一个局部变量;
- pthread_exit或return返回的指针指向的内存单元必须是全局或是用malloc分配的,不能在线程函数的栈上分配,因为当其他线程得到这个返回指针时线程函数已经退出;
int pthread_cancel(pthread_t thread);
- 成功返回0,失败返回错误码;
线程等待
为何需要线程等待:
int pthread_join(pthread_t thread, void** value_ptr);
- value_ptr,指向一个指针,然后在指向线程的返回值;
- 成功返回0,失败返回错误码;
调用该函数的线程将挂起等待,直到id为thread的线程终止;thread的线程以不同的方法终止,通过pthread_join得到的终止状态也是不同的:
#include
#include
#include
#include
void* thread1(void* arg){
printf("thread1 returning ...\n");
int *p = (int*)malloc(sizeof(int));
*p = 1;
return (void*)p;
}
void* thread2(void* arg){
printf("thread1 exiting ...\n");
int *p = (int*)malloc(sizeof(int));
*p = 2;
pthread_exit((void*)p);
}
void* thread3(void* arg){
while(1){
printf("thread3 running ...\n");
sleep(1);
}
return NULL;
}
int main(){
pthread_t tid;
void* ret;
//线程1,return
pthread_create(&tid, NULL, thread1, NULL);
pthread_join(tid, &ret);
printf("thread1 return, thread id %x, return code: %d\n", tid, *(int*)ret);
free(ret);
//线程2,exit
pthread_create(&tid, NULL, thread2, NULL);
pthread_join(tid, &ret);
printf("thread2 return, thread id %x, return code: %d\n", tid, *(int*)ret);
free(ret);
//线程3,cancel by other
pthread_create(&tid, NULL, thread3, NULL);
sleep(3);
pthread_cancel(tid);
pthread_join(tid, &ret);
if(ret == PTHREAD_CANCELED)
printf("thread3 return, thread id %x, return code: PTHREAD_CANCELED\n", tid);
else
printf("thread3 return, thread id %x, return code: NULL\n", tid);
}
[wz@192 Desktop]$ gcc -o test test.c -lpthread
[wz@192 Desktop]$ ./test
thread1 returning ...
thread1 return, thread id 2d3f6700, return code: 1
thread1 exiting ...
thread2 return, thread id 2d3f6700, return code: 2
thread3 running ...
thread3 running ...
thread3 running ...
thread3 return, thread id 2d3f6700, return code: PTHREAD_CANCELED
线程分离
int pthread_detach(pthread_t thread);
//可是线程组内其他线程对目标线程进行分离,也可是线程自己分离
pthread_detach(pthread_self());
#include
#include
#include
#include
void* thread_run(void* arg){
pthread_detach(pthread_self());
printf("%s\n", (char*)arg);
return NULL;
}
int main(){
pthread_t tid;
if(pthread_create(&tid, NULL, thread_run, "thread run ...\n") != 0){
printf("create thread error\n");
return 1;
}
int ret = 0;
sleep(1); //很重要,要让线程先分离,在等待
if(pthread_join(tid, NULL) == 0){
printf("pthread wait success\n");
ret = 0;
} else{
printf("pthread wait failed\n");
ret = 1;
}
return ret;
}
[wz@192 Desktop]$ gcc -o test test.c -lpthread
[wz@192 Desktop]$ ./test
thread run ...
pthread wait failed
linux线程互斥
互斥量mutex
#include
#include
#include
#include
#include
int ticket = 100;
void* route(void* arg){
char* id = (char*)arg;
while(1){
if(ticket > 0){
usleep(1000);
printf("%s sells ticket: %d\n", id, ticket);
ticket--;
}
else break;
}
}
int main(){
pthread_t t1, t2, t3, t4;
pthread_create(&t1, NULL, route, "thread1");
pthread_create(&t2, NULL, route, "thread2");
pthread_create(&t3, NULL, route, "thread3");
pthread_create(&t4, NULL, route, "thread4");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
}
[wz@192 Desktop]$ gcc -o test test.c -lpthread
[wz@192 Desktop]$ ./test
thread1 sells ticket: 100
thread3 sells ticket: 100
thread4 sells ticket: 98
thread2 sells ticket: 100
thread1 sells ticket: 96
thread4 sells ticket: 95
thread2 sells ticket: 94
...
thread3 sells ticket: 4
thread4 sells ticket: 2
thread1 sells ticket: 2
thread3 sells ticket: 0
thread2 sells ticket: 0
thread4 sells ticket: -2
- 代码必须要有互斥行为,当代码进入临界区执行时,不允许其他线程进入该临界区;
- 如多个线程同时要求执行临界区的代码,并且临界区没有线程执行,那么只能允许一个线程进入临界区;
- 如线程不再临界区中执行,那么该线程不能阻止其他线程进入临界区;
互斥量接口
初始互斥量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
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_unlock(pthread_mutex_t* mutex);
调用pthread_lock时,可能会遇到以下问题:
- 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功;
- 发起函数调用时,其他线程已锁定互斥量,或存在线程同时申请互斥量,但没有竞争到互斥量,那么调用会阻塞(执行流被挂起),等待互斥量解锁;
#include
#include
#include
#include
#include
int ticket = 100;
pthread_mutex_t mutex;
void* route(void* arg){
char* id = (char*)arg;
while(1){
pthread_mutex_lock(&mutex);
if(ticket > 0){
usleep(1000);
printf("%s sells ticket: %d\n", id, ticket);
ticket--;
pthread_mutex_unlock(&mutex);
}
else{
pthread_mutex_unlock(&mutex);
break;
}
}
}
int main(){
pthread_t t1, t2, t3, t4;
pthread_mutex_init(&mutex, NULL);
pthread_create(&t1, NULL, route, "thread1");
pthread_create(&t2, NULL, route, "thread2");
pthread_create(&t3, NULL, route, "thread3");
pthread_create(&t4, NULL, route, "thread4");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
pthread_mutex_destroy(&mutex);
}
[wz@192 Desktop]$ gcc -o test test.c -lpthread
[wz@192 Desktop]$ ./test
thread1 sells ticket: 100
thread1 sells ticket: 99
thread1 sells ticket: 98
thread1 sells ticket: 97
thread1 sells ticket: 96
...
thread1 sells ticket: 5
thread1 sells ticket: 4
thread1 sells ticket: 3
thread1 sells ticket: 2
thread1 sells ticket: 1
互斥量实现原理
可重入和线程安全
常见线程不安全情况
- 不保护共享变量的函数;
- 函数状态随着被调用,状态发生变化的函数;
- 返回指向静态变量指针的函数;
- 调用线程不安全函数的函数;
常见线程安全的情况
- 每个线程对全局变量或静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的;
- 类或接口对于线程来说都是原子操作;
- 多个线程之间的切换不会导致该接口的执行结果存在二义性;
常见不可重入情况
- 调用了malloc/free函数,因malloc函数是全局链表来管理堆的;
- 调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构;
- 可重入函数体内使用了静态的数据结构;
常见可重入情况
- 不使用全局变量或静态变量;
- 不使用malloc/new开辟的空间;
- 不调用不可重入函数;
- 不返回静态或全局数据,所有数据都由函数的调用者提供;
- 使用本地数据,或通过制作全局数据的本地拷贝来保护全局数据;
可重入与线程安全联系
可重入与线程安全区别