个人学习随笔,仅做学习之用
【1】线程的概念
线程:是一个程序并发执行多种任务的机制。
并发:多个任务同时运行。(cpu以ns级别的速度进行进程调度,切换进程、线程)
进程上下文切换:
上下文:运行一个程序所需要的所有资源;
上下文切换:替换原有内容,是一个耗时操作。
为了提高系统的性能,引入了一个轻量级的进程概念,称之为线程。
线程:属于进程,每一个进程至少需要一个线程作为指令执行体,线程运行在进程空间内。
多线程:一个进程,可以运行多个线程。
线程是程序运行的最小单位
同一个进程下的线程,共享该进程的内存地址(每个线程共享其附属进程的所有资源)
-
资源分配
线程与线程之间共享进程的:
1)数据段(全局变量,静态变量,堆)
2)正文段
3)文件描述符
不共享:
1)栈区:每个线程都有自己的展区,存放各自的局部变量
进程与线程的区别
-
进程之间,都有独立的虚拟空间,是独立的个体
因此,进程与进程之间要实现数据传输,需要引入进程通信机制
-
属于同一进程下的线程,共享进程的虚拟地址空间,不需要通信机制。
-
进程是资源分配的最小单位,线程是程序执行的最小单位
-
创建子进程需要克隆父进程的所有资源,但是创建线程不需要,因为本身就共享进程资源。
所以,创建多线程,比创建子进程效率高。
【2】线程常用的函数
man pthread_create
1)安装man手册
sudo apt-get install manpages-de manpages-de-dev manpages-dev glibc-doc manpages-posix-dev manpages-posix
Compile and link with -pthread
2)pthread_create
功能:创建线程;
头文件:
#include <pthread.h>
原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
参数:
pthread_t *thread:存储创建后的线程;
pthread_attr_t *attr:线程属性,填NULL,代表默认属性;
void *(*start_routine) (void *):是线程的执行体,函数指针。回调函数。
需要传入一个函数的地址,该指针能指向 返回值是void*类型,且参数列表是void*类型的函数;
void *arg:传入回调函数的参数;
返回值:
成功,返回0;
失败,返回错误码.非0;
注意:
- 线程是依附于进程的,如果进程退出(主线程退出),则同一进程下的所有线程都会强制退出。
- 分支线程退出,不会影响其他线程运行;
- 主线程和分支线程是并行的,谁先运行不确定,一般来说,主线程会先运行。
3)pthread_exit
功能:退出当前线程;
头文件:
#include <pthread.h>
原型:
void pthread_exit(void *retval);
参数:
void *retval:线程退出的状态值,可以是任意类型的数据地址,也可以填NULL;
可以被pthread_join函数接收。
没有pthread_join,线程的资源没有被回收,类似进程的僵尸进程。
4)pthread_join
功能:阻塞等待子线程退出,并回收线程资源(相当于进程中的wait函数);
头文件:
#include <pthread.h>
原型:
int pthread_join(pthread_t thread, void **retval);
参数:
pthread_t thread:指定要等待的线程;
void **retval:存储的是 pthread_exit(void *retval)的参数的地址,如果不想接收,填NULL;
返回值:
成功,返回0;
失败,返回errno;
#include <stdio.h>
#include <pthread.h>
#include <string.h>
//方法2
int status = 2;
//线程执行体
void* handler(void*arg)
{
printf("******线程******\n");
//方法1
//static int status = 2;
// pthread_exit(&status);
//方法3:
pthread_exit((void*)2);
}
void* handler1(void*arg)
{
sleep(1);
printf("******线程1******\n");
return NULL;
}
int main(int argc, const char *argv[])
{
pthread_t tid1, tid2;
if(pthread_create(&tid1, NULL, handler, NULL) != 0)
{
perror("pthread_create");
return -1;
}
if(pthread_create(&tid2, NULL, handler1, NULL) != 0)
{
perror("pthread_create");
return -1;
}
printf("主函数\n");
/*
//方法1,2
int *status;
pthread_join(tid1, (void**)&status);
printf("%d\n", *status);
*/
//方法3
int status;
pthread_join(tid1, (void**)&status);
printf("%d\n", status);
return 0;
}
5)pthread_cancel
功能:请求指定线程退出;
头文件:
#include <pthread.h>
原型:
int pthread_cancel(pthread_t thread);
参数:
pthread_t thread:指定要退出的线程;发送成功,并不意味着线程退出;
返回值:
成功,返回0;
失败,返回error;
6)pthread_detach
功能:分离线程,线程退出后,自动回收线程资源;
头文件:
#include <pthread.h>
原型:
int pthread_detach(pthread_t thread);
参数:
pthread_t thread:指定要分离的线程id;
返回值:
成功,返回0;
失败,返回错误码;
获取当前线程的tid号:
pthread_self();
pthread_detach(pthread_self());
【3】线程的同步互斥机制
临界资源(共享资源):多个任务并发执行的时候,访问同一个资源,我们将这个资源称之为临界资源。
临界区:访问临界资源的代码,称之为临界区;
线程之间,如果要进行通信,需要引入同步互斥机制,避免产生竞态。保证任何一个时刻,只有一个线程处理临界资源。
同步互斥机制:
- 互斥锁(pthread_mutex_t)
- 条件变量(pthread_cond_t)
- 信号量(sem_t)
1. 互斥锁
1)工作原理
-
对于要访问临界资源的线程,在访问之前,先申请互斥锁。
如果申请上锁成功,则执行临界区代码,直到退出临界区,解开互斥锁。
如果申请上锁失败,则说明互斥锁被别的线程占用,则线程进入休眠,等待互斥锁打开。
-
互斥锁会将临界区锁住,保证了临界区代码的完整性。
-
互斥锁又称之为二值信号量
2)pthread_mutex_init
功能:创建并初始化互斥锁;
头文件:
#include <pthread.h>
原型:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;
参数:
pthread_mutex_t *mutex:存储申请及初始化后的互斥锁;
pthread_mutexattr_t *mutexattr:互斥锁的属性,填NULL;
返回值:
成功,返回0;
失败,返回errno;
用宏的方式初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
3)pthread_mutex_lock
功能:上锁;
头文件:
#include <pthread.h>
原型:
int pthread_mutex_lock(pthread_mutex_t *mutex);
参数:
pthread_mutex_t *mutex:需要上锁的互斥锁的地址;
返回值:
成功,返回0;
失败,返回errno;
4)pthread_mutex_unlock
功能:解锁;
头文件:
#include <pthread.h>
原型:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数:
pthread_mutex_t *mutex:需要解开的互斥锁的地址;
返回值:
成功,返回0;
失败,返回errno;
5)pthread_mutex_destroy
功能:销毁初始化;
头文件:
#include <pthread.h>
原型:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
返回值:
pthread_mutex_t *mutex:要销毁的互斥锁的地址;
返回值:
成功,返回0;
失败,返回errno;
#include <stdio.h>
#include <string.h>
#include <pthread.h>
//临界资源(共享资源)
char str[30] = "AAAAAAAAAAA|BBBBBBBBBB";
//互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* handler1(void* arg)
{
while(1)
{
/*******临界区******/
pthread_mutex_lock(&mutex);
printf("%s\n", str);
pthread_mutex_unlock(&mutex);
/*******临界区******/
}
pthread_exit(NULL);
}
//倒置
void* handler2(void* arg)
{
char* start = str;
char* end = str+strlen(str)-1;
char temp = 0;
while(1)
{
start = str;
end = str+strlen(str)-1;
/*******临界区******/
pthread_mutex_lock(&mutex);
while(start < end)
{
temp = *start;
*start = *end;
*end = temp;
start++;
end--;
}
pthread_mutex_unlock(&mutex);
/*******临界区******/
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
/*
//创建并初始化互斥所
if(pthread_mutex_init(&mutex, NULL)!=0)
{
perror("pthread_mutex_init");
return -1;
}
*/
pthread_t tid1, tid2;
if(pthread_create(&tid1, NULL, handler1, NULL) != 0)
{
perror("pthread_create");
return -1;
}
if(pthread_create(&tid2, NULL, handler2, NULL) != 0)
{
perror("pthread_create");
return -1;
}
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
//销毁互斥所
pthread_mutex_destroy(&mutex);
return 0;
}
6)举例:
用两个线程拷贝一张图片,A线程拷贝前半部分,B线程拷贝后半部分
不能使用sleep函数
int size = lseek(fd_r, 0, SEEK_END);
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
typedef struct
{
int fd_r;
int fd_w;
int size;
}__msg;
//互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//拷贝前半部分
void* handler1(void* arg)
{
__msg msg = *(__msg*)arg;
/********临界区********/
pthread_mutex_lock(&mutex);
lseek(msg.fd_r, 0, SEEK_SET);
lseek(msg.fd_w, 0, SEEK_SET);
int i = 0;
char c = 0;
int res = 0;
for(i=0; i<msg.size/2; i++)
{
res = read(msg.fd_r, &c, 1);
if(res < 0)
{
perror("read");
pthread_mutex_unlock(&mutex);
pthread_exit(NULL);
}
if(write(msg.fd_w, &c, 1) < 0)
{
perror("write");
pthread_mutex_unlock(&mutex);
pthread_exit(NULL);
}
}
printf("前半部分拷贝完毕\n");
pthread_mutex_unlock(&mutex);
/********临界区********/
pthread_exit(NULL);
}
//拷贝后半部分
void* handler2(void* arg)
{
__msg msg = *(__msg*)arg;
/********临界区********/
pthread_mutex_lock(&mutex);
lseek(msg.fd_r, msg.size/2, SEEK_SET);
lseek(msg.fd_w, msg.size/2, SEEK_SET);
int i = 0;
char c = 0;
int res = 0;
while(1)
{
res = read(msg.fd_r, &c, 1);
if(res < 0)
{
perror("read");
break;
}
else if(0 == res)
{
break;
}
if(write(msg.fd_w, &c, 1) < 0)
{
perror("write");
pthread_mutex_unlock(&mutex);
pthread_exit(NULL);
}
}
printf("后半部分拷贝完毕\n");
pthread_mutex_unlock(&mutex);
/********临界区********/
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
__msg msg;
msg.fd_r = open("1.png", O_RDONLY);
msg.fd_w = open("2.png", O_WRONLY|O_CREAT|O_TRUNC, 0664);
if(msg.fd_r < 0 || msg.fd_w < 0)
{
perror("open");
return -1;
}
//计算大小
msg.size = lseek(msg.fd_r, 0, SEEK_END);
lseek(msg.fd_r, 0, SEEK_SET);
pthread_t tid1, tid2;
if(pthread_create(&tid1, NULL, handler1, (void*)&msg) != 0)
{
perror("pthread_create");
return -1;
}
if(pthread_create(&tid2, NULL, handler2, (void*)&msg) != 0)
{
perror("pthread_create");
return -1;
}
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
//销毁互斥所
pthread_mutex_destroy(&mutex);
close(msg.fd_r);
close(msg.fd_w);
return 0;
}
7)死锁
拥有锁资源的任务,没有释放锁
- 持有互斥锁的任务,异常退出,没有释放锁资源。
- 同一互斥锁重复上锁。
- 互斥锁交叉嵌套
2. 条件变量
1)工作原理
- 将不访问共享资源的线程直接睡眠
- 如果线程需要访问,则通过其他线程唤醒。
2)pthread_cond_init
功能:创建并初始化条件变量;
头文件:
#include <pthread.h>
原型:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
参数:
pthread_cond_t *cond:存储初始化后的条件变量;
pthread_condattr_t *cond_attr:填NULL;
返回值:
成功,返回0;
失败,返回errno;
3)pthread_cond_wait
功能:让线程进入睡眠,等待被唤醒;
头文件:
#include <pthread.h>
原型:
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
参数:
pthread_cond_t *cond:指定的条件变量;
pthread_mutex_t *mutex:指定互斥锁;
返回值:
成功,返回0;
失败,返回errno;
函数的步骤:
1.解开互斥锁,同时阻塞当前线程;
2.等待被唤醒;
3.唤醒成功后,尝试上锁;
4.如果上锁成功,则从当前位置继续向后运行;
5.如果上锁失败,则从当前位置继续阻塞;
注意:
唤醒,且成功上锁后,是从上一次睡觉的地方继续运行的。不会从线程头部开始运行。
4)pthread_cond_signal
功能:唤醒条件变量;
头文件:
#include <pthread.h>
原型:
int pthread_cond_signal(pthread_cond_t *cond);
参数:
pthread_cond_t *cond:指定要唤醒的条件变量;
返回值:
成功,返回0;
失败,返回errno;
5)pthread_cond_destroy
功能:销毁条件变量;
头文件:
#include <pthread.h>
原型:
int pthread_cond_destroy(pthread_cond_t *cond);
参数:
pthread_cond_t *cond:指定要销毁的互斥锁;
返回值:
成功,返回0;
失败,返回errno;
#include <stdio.h>
#include <string.h>
#include <pthread.h>
//互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int flag = 1; //1.海绵宝宝,2.天线宝宝
//打印海绵宝宝
void* handler1(void* arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
//如果不是海绵宝宝,则进入if条件为真
if(flag != 1)
{
//1.让当前线程休眠
//2.打开互斥锁
pthread_cond_wait(&cond, &mutex);
}
printf("*****海绵宝宝\n");
flag = 2;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);
}
pthread_exit(NULL);
}
//打印天线宝宝
void* handler2(void* arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
if(flag != 2)
{
//1.让当前线程休眠
//2.打开互斥锁
pthread_cond_wait(&cond, &mutex);
//3.被唤醒后,尝试上锁
//4.如果上锁失败,则继续休眠
//5.如果上锁成功,则从当前位置继续向下执行.
}
printf("--天线宝宝\n");
flag = 3;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);
}
pthread_exit(NULL);
}
/*
//打印花园宝宝
void* handler3(void* arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
if(flag != 3)
{
//1.让当前线程休眠
//2.打开互斥锁
pthread_cond_wait(&cond, &mutex);
//3.被唤醒后,尝试上锁
//4.如果上锁失败,则继续休眠
//5.如果上锁成功,则从当前位置继续向下执行.
}
printf("++++++++++++++花园宝宝\n");
flag = 1;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);
}
pthread_exit(NULL);
}
*/
int main(int argc, const char *argv[])
{
/*
//创建并初始化互斥所
if(pthread_mutex_init(&mutex, NULL)!=0)
{
perror("pthread_mutex_init");
return -1;
}
//创建并初始化条件变量
if(pthread_cond_init(&cond, NULL) != 0)
{
perror("pthread_cond_init");
return -1;
}
*/
pthread_t tid1, tid2, tid3;
if(pthread_create(&tid1, NULL, handler1, NULL) != 0)
{
perror("pthread_create");
return -1;
}
if(pthread_create(&tid2, NULL, handler2, NULL) != 0)
{
perror("pthread_create");
return -1;
}
/*
if(pthread_create(&tid3, NULL, handler3, NULL) != 0)
{
perror("pthread_create");
return -1;
}
*/
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
//销毁互斥所
pthread_mutex_destroy(&mutex);
//销毁互斥所
pthread_cond_destroy(&cond);
return 0;
}
6)条件变量的基本格式
根据需求修改flag和todo something位置
#include <stdio.h>
#include <pthread.h>
//初始化条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int flag = 1;
void* handler1(void* arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
if(flag != 1)
{
//1.让当前线程休眠
//2.打开互斥锁
pthread_cond_wait(&cond, &mutex);
//3.被唤醒后,尝试上锁
//4.如果上锁失败,继续休眠
//5.如果上锁成功,则从当前位置继续向下运行
}
//todo something
printf("111111\n");
flag = 2;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);
}
pthread_exit(NULL);
}
void* handler2(void* arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
if(flag != 2)
{
//1.让当前线程休眠
//2.打开互斥锁,
pthread_cond_wait(&cond, &mutex);
//3.被唤醒后,立即上锁
//4.如果上锁失败,继续休眠
//5.如果上锁成功,则从当前位置继续向下运行
}
//todo something
printf("2222222\n");
flag = 1;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, handler1, NULL);
pthread_create(&tid2, NULL, handler2, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
return 0;
}
3. 信号量
1)工作原理
-
对于访问共享资源的线程,都去执行申请信号量的操作。
当信号量大于0,如果申请信号量成功,则信号量-1;
当信号量为0,则申请信号量操作会阻塞,线程进入休眠等待阶段。
-
互斥锁又称之为二值信号量,只允许一个线程进入临界区
-
信号量允许多个线程同时进入临界区
-
PV操作:实现线程、进程同步互斥的有效方式
P:申请信号量,-1操作
V:释放信号,+1操作
2)sem_init
功能:创建并初始化信号量;
头文件:
#include <semaphore.h>
原型:
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
sem_t *sem:存储创建并初始化后的信号量;
int pshared:共享标识;
0,用于线程;
非0,用于进程;
unsigned int value:信号量的初始值;
返回值:
成功,返回0;
失败,返回-1,更新errno;
3)sem_destroy
#include <semaphore.h>
int sem_destroy(sem_t *sem);
4)sem_wait(-1)
功能:申请信号量;P操作:-1;
1.如果申请前,信号量为0,则该线程阻塞,进入休眠;
2.如果申请前,信号量大于0,则申请成功,信号量-1,函数立即返回;
头文件:
#include <semaphore.h>
原型:
int sem_wait(sem_t *sem);
参数:
sem_t *sem:指定需要申请的信号量;
返回值:
成功,返回0;
失败,返回-1;
5)sem_post(+1)
功能:释放信号量,V操作:+1;
头文件:
#include <semaphore.h>
原型:
int sem_post(sem_t *sem);
参数:
sem_t *sem:指定信号量;
返回值:
成功,返回0;
失败,返回-1,更新errno;
6)sem_getvalue
功能:获取信号量的值;
头文件:
#include <semaphore.h>
原型:
int sem_getvalue(sem_t *sem, int *sval);
参数:
sem_t *sem:指定信号量;
int *sval:存储获取到的信号量的值;
返回值:
成功,返回0;
失败,返回-1,更新errno;
7)举例
1.用信号量的方式实现一个线程倒置,一个线程打印
2.用信号量的方式实现倒置一次打印一次。
例1:
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
//临界资源(共享资源)
char str[30] = "AAAAAAAAAAA|BBBBBBBBBB";
//互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* handler1(void* arg)
{
while(1)
{
/*******临界区******/
// pthread_mutex_lock(&mutex);
sem_wait((sem_t*)arg);
printf("%s\n", str);
// pthread_mutex_unlock(&mutex);
sem_post((sem_t*)arg);
/*******临界区******/
}
pthread_exit(NULL);
}
//倒置
void* handler2(void* arg)
{
char* start = str;
char* end = str+strlen(str)-1;
char temp = 0;
while(1)
{
start = str;
end = str+strlen(str)-1;
/*******临界区******/
//pthread_mutex_lock(&mutex);
sem_wait((sem_t*)arg);
while(start < end)
{
temp = *start;
*start = *end;
*end = temp;
start++;
end--;
}
//pthread_mutex_unlock(&mutex);
sem_post((sem_t*)arg);
/*******临界区******/
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
sem_t sem;
if(sem_init(&sem, 0, 1) < 0)
{
perror("sem_init");
return -1;
}
pthread_t tid1, tid2;
if(pthread_create(&tid1, NULL, handler1, (void*)&sem) != 0)
{
perror("pthread_create");
return -1;
}
if(pthread_create(&tid2, NULL, handler2, (void*)&sem) != 0)
{
perror("pthread_create");
return -1;
}
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
//销毁互斥所
pthread_mutex_destroy(&mutex);
return 0;
}
例2:
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
//临界资源(共享资源)
char str[30] = "AAAAAAAAAAA|BBBBBBBBBB";
sem_t sem1;
sem_t sem2;
void* handler1(void* arg)
{
while(1)
{
/*******临界区******/
sem_wait(&sem1);
printf("%s\n", str);
// pthread_mutex_unlock(&mutex);
sem_post(&sem2);
/*******临界区******/
}
pthread_exit(NULL);
}
//倒置
void* handler2(void* arg)
{
char* start = str;
char* end = str+strlen(str)-1;
char temp = 0;
while(1)
{
start = str;
end = str+strlen(str)-1;
/*******临界区******/
sem_wait(&sem2);
while(start < end)
{
temp = *start;
*start = *end;
*end = temp;
start++;
end--;
}
sem_post(&sem1);
/*******临界区******/
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
//sem1控制打印
if(sem_init(&sem1, 0, 1) < 0)
{
perror("sem_init");
return -1;
}
//sem2控制倒置
if(sem_init(&sem2, 0, 0) < 0)
{
perror("sem_init");
return -1;
}
pthread_t tid1, tid2;
if(pthread_create(&tid1, NULL, handler1, NULL) != 0)
{
perror("pthread_create");
return -1;
}
if(pthread_create(&tid2, NULL, handler2, NULL) != 0)
{
perror("pthread_create");
return -1;
}
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
//销毁信号量
sem_destroy(&sem1);
sem_destroy(&sem2);
return 0;
}