多线程与多进程一样,为了能同时执行多个任务
区别
多进程
创建子进程,子进程会拷贝父进程的数据段的所有内存
进程是资源的获取单位
每个进程完全独立运行
更加关注两个进程之间的通信问题
多线程
线程是进程的最小组成单位,每个进程至少有一个线程(main函数所在的线程,称为主线程)
创建一个线程,会和原来的进程共享一个内存
线程是资源的分配单位
线程并不完全独立,主线程如果结束运行,整个进程就结束了,该进程中的其他线程也会随之结束(除了主线程外,其他线程结束运行没有影响)
更加关注先后运行的问题
多线程中的名词解释
临界数据
多个线程都能访问的数据
临界区
多个线程都能使用的代码,访问临界数据
竞态
多个线程同时想要访问临界数据时
互斥
多个线程同时访问临界数据时,只有一个线程允许访问临界数据,其他所有线程不允许访问的形式
同步
在互斥的基础上,安排好多个线程之间的运行顺序,使得每个线程的运行顺序都可预测
异步
多线程准备工作
sudo apt-get install manpages-pl-dev manpages-posix-dev //安装手册
创建并运行线程
pthread_create函数
函数原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *),void *arg);
调用形式:
void* task (void *arg)
{
}
pthread_t=id;
pthread_create(&id,0,task,0);
功能:
创建一个线程并运行该线程
参数thread:用来接收新创建的线程的id号的变量的指针
参数attr(属性attribute的缩写):线程的属性(需要传入一个attr属性并且是指针类型时,直接传入0,表示默认属性)
参数start_routine:函数指针(该参数需要一个函数名),传入的函数就是线程创建成功后区运行的函数(传入不同的函数,创建成功的线程就会运行不同的代码)
参数arg:本质上会在pthread_create函数内部,传递给start_routine这个函数指针的的参数
- 注意:线程函数库不会自动链接.o文件,所以每次编译的时候,需要手动连接,在编译的最后加上“-lpthread”(-l:表示链接函数库。pthread:表示函数库的库名)
线程的资源回收
每开启一个线程,也是需要8kb的内存空间。所以如果一个线程结束,这8kb的资源也需要回收
pthread_join函数
函数原型:
int pthread_join(pthread_t thread, void **retval);
调用形式:
void* retval;
pthread_join(id,&retval);
功能:
等待线程id为thread的线程结束运行,并回收其资源。效果wait和waitpid是一样的
参数thread:等待资源回收的线程id
参数retval:传入一个void**的指针,所以在pthread_join函数内部,被这个指针指向的void*的值要被修改,retval指向的void*数据,会在pthread_join函数内部,去接收start_routine函数的返回值
pthread_detach函数
将线程的属性设置为分离式属性,分离式属性的线程,会在所属的进程结束运行的时候自动回收资源
函数原型:
int pthread_detach(pthread_t thread);
调用形式:
pthread_detach(id);
功能:
将线程的属性设置为分离式属性
- pthread_detach不会阻塞以等待线程结束运行
线程属性设置流程
- 创建一个线程属性的变量
pthread_attr_t attr;
- 初始化线程属性
pthread_attr_init函数
函数原型:
int pthread_attr_init(pthread_attr_t *attr);
调用形式:
pthread_attr_init(&attr);
功能:
初始化线程属性
线程属性和默认值
Detach state = PTHREAD_CREATE_JOINABLE
Scope = PTHREAD_SCOPE_SYSTEM
Inherit scheduler = PTHREAD_INHERIT_SCHED
Scheduling policy = SCHED_OTHER
Scheduling priority = 0
Guard size = 4096 bytes
Stack address = 0x40196000
Stack size = 0x201000 bytes
上述每个属性,都有一个对应的set和get函数去设置和获取属性
- 使用每个线程对应的set函数去设置线程的属性
线程如何结束运行
进程结束运行:
- main指向return
- 调用exit或_exit
线程结束运行:
- 线程入口函数(线程运行的那个函数)执行return
- 调用pthread_exit
函数原型:
void pthread_exit(void *retval);
调用形式:
pthread_exit(0);
功能:
立刻结束当前线程的运行
参数retval:实际上就是传递给当前线程正在运行的那个函数的返回值
- 调用pthread_cancel
函数原型
int pthread_cancel(pthread_t thread);
调用形式
pthread_cancel(id)
功能描述:取消线程id为thread的线程的运行
注意:pthread_cancel只是登记需要结束的线程,登记之后其实是不会立刻结束的
只有当线程运行到下一个退出点的时候,才会退出运行
为了保证pthread_cancel调用之后,一定不会运行后面的代码
可以手动的测试,该线程是否被登记取消运行
- pthread_testcancel
void pthread_testcancel(void);
功能描述:立刻测试当前线程是否被cancel,如果被cancel了则退出线程的运行
- pthread_setcanceltype
int pthread_setcanceltype(int type, int *oldtype);
功能描述:设置当前线程,是否允许通过 pthread_cancel 取消运行
参数 type:有以下选项:
PTHREAD_CANCEL_ENABLE:
允许通过cancel函数取消线程的运行,这个是默认选项
PTHREAD_CANCEL_DISABLE
胡允许通过cancel函数取消线程的运行
参数 oldtype:
用来记录在更改线程取消状态之前,线程原先的取消状态,如果不需要记录,则直接传0即可
- pthread_setcancelstate
int pthread_setcanceltype(int type, int *oldtype);
功能描述:设置当前线程,是否允许通过 pthread_cancel 取消运行
参数 type:有以下选项:
PTHREAD_CANCEL_ENABLE:
允许通过cancel函数取消线程的运行,这个是默认选项
PTHREAD_CANCEL_DISABLE
胡允许通过cancel函数取消线程的运行
参数 oldtype:
用来记录在更改线程取消状态之前,线程原先的取消状态,如果不需要记录,则直接传0即可
同步与互斥
为什么要互斥
互斥是为了保证多个线程同时运行时,只允许一个线程完整访问临界数据,从而达到保护临界数据的目的
逻辑上实现互斥效果
在一个线程彻底访问完临界数据之前,其他所有会访问同样的临界数据的线程都不能进行访问
逻辑上实现同步效果
在一个线程彻底访问完临界数据之后,其他所有会访问同样的临界数据的线程都不能进行访问
注意
上面所有代码,互斥使用都是自己写的逻辑实现。
我们自己写的互斥逻辑,在运行过程当中,本身就会本其他线程入侵,导致互斥失败
所以,我们在做互斥操作的时候,一定要保证互斥本身是完整的一套操作,不会被其他线程入侵
这样的操作,有一个名词叫做:"原子操作"
互斥锁
什么是互斥锁
互斥锁本身是一个数量为1的临界数据,但这个临界数据有原子性,在操作的过程中,不会被其他线程入侵。
互斥锁的工作原理
互斥锁本身是一个数量为1的临界数据
只要有一个线程获取互斥锁(上锁),此时互斥锁数量变为0,此时别的进程获取不了互斥锁(处于阻塞状态),直到第一个线程释放互斥锁(解锁),此时别的线程就可以获取互斥锁(阻塞解除)
互斥锁的操作流程
- 创建一个全局互斥锁变量
pthread_mutex_t mutex;
- 初始化互斥锁
pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;
这种形式为:静态创建互斥锁
优点就是:方便快捷
缺点就是:无论静态创建几把互斥锁,内核里面实际上永远是同一把互斥锁
所以一般不推荐静态创建
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutex‐
attr);
调用形式:
pthread_mutex_init(&mutex,0);
功能描述:初始化互斥锁
参数 mutex:准备初始化的互斥锁的地址
参数 mutex-attr:传0,表示创建一把默认的互斥
- 上锁/解锁(抢互斥锁/释放互斥锁)
int pthread_mutex_lock(pthread_mutex_t *mutex);
调用形式:
pthread_mutex_lock(&mutex);
功能描述:上锁一把互斥锁
参数mutex:准备上锁的互斥锁的指针
int pthread_mutex_unlock(pthread_mutex_t *mutex);
调用形式:
pthread_mutex_unlock(&mutex);
功能描述:解锁一把互斥锁
参数mutex:准备解锁的互斥锁指针
- 不再使用互斥锁需要销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
调用形式:
pthread_mutex_destroy(&mutex);
互斥锁实现互拆的模型
线程1:
while(1){
pthread_mutex_lock(&mutex)
访问临界资源
访问临界资源
......
pthread_mutex_unlock(&mutex)
}
线程2:
while(1){
pthread_mutex_lock(&mutex)
访问临界资源
访问临界资源
......
pthread_mutex_unlock(&mutex)
}
在所有线程访问临界数据的前后使用互斥锁锁住
互斥锁实现同步
互斥锁实现同步的模型
保证线程1先运行:
上锁线程2的互斥锁
线程1:
while(1){
上锁自己的互斥锁
.............
解锁下一个要运行的线程的互斥锁
}
线程2:
while(1){
上锁自己的互斥锁
.............
解锁下一个要运行的线程的互斥锁
}
有几个线程参与同步,就需要几把互斥锁
先上锁不运行的线程的互斥锁
每一个线程运行之前,再一次上锁自己的互斥锁
每一个线程运行结束之后,解锁下一个要运行的线程的互斥锁
练习
创建2个线程
1#线程:负责文件IO向文件中写入数据
2#线程:从该文件中读取数据
使用互拆锁
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <wait.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/un.h>
typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;
int a = 0;
pthread_mutex_t ma; //创建五个互斥锁
pthread_mutex_t mb;
pthread_mutex_t mc;
pthread_mutex_t md;
pthread_mutex_t me;
void* task1(void* arg){
while(1){
pthread_mutex_lock(&mb);
printf("2\n");
sleep(1);
pthread_mutex_unlock(&mc);
}
}
void* task2(void* arg){
while(1){
pthread_mutex_lock(&mc);
printf("3\n");
sleep(1);
pthread_mutex_unlock(&md);
}
}
void* task3(void* arg){
while(1){
pthread_mutex_lock(&md);
printf("4\n");
sleep(1);
pthread_mutex_unlock(&me);
}
}
void* task4(void* arg){
while(1){
pthread_mutex_lock(&me);
printf("5\n");
sleep(1);
pthread_mutex_unlock(&ma);
}
}
int main(int argc, const char *argv[])
{
pthread_mutex_init(&ma,0); //初始化五个互斥锁,并把不需要的互斥锁上锁
pthread_mutex_init(&mb,0);
pthread_mutex_lock(&mb);
pthread_mutex_init(&mc,0);
pthread_mutex_lock(&mc);
pthread_mutex_init(&md,0);
pthread_mutex_lock(&md);
pthread_mutex_init(&me,0);
pthread_mutex_lock(&me);
pthread_t id1,id2,id3,id4; //创建四个线程
pthread_create(&id1,0,task1,0);
pthread_detach(id1);
pthread_create(&id2,0,task2,0);
pthread_detach(id2);
pthread_create(&id3,0,task3,0);
pthread_detach(id3);
pthread_create(&id4,0,task4,0);
pthread_detach(id4);
while(1){
pthread_mutex_lock(&ma);
printf("1\n");
sleep(1);
pthread_mutex_unlock(&mb);
}
return 0;
}
创建5个线程,使用互斥锁安排这五个线程同步运行:1234512345
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <wait.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/un.h>
typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;
int a = 0;
pthread_mutex_t ma; //创建五个互斥锁
pthread_mutex_t mb;
pthread_mutex_t mc;
pthread_mutex_t md;
pthread_mutex_t me;
void* task1(void* arg){
while(1){
pthread_mutex_lock(&mb);
printf("2\n");
sleep(1);
pthread_mutex_unlock(&mc);
}
}
void* task2(void* arg){
while(1){
pthread_mutex_lock(&mc);
printf("3\n");
sleep(1);
pthread_mutex_unlock(&md);
}
}
void* task3(void* arg){
while(1){
pthread_mutex_lock(&md);
printf("4\n");
sleep(1);
pthread_mutex_unlock(&me);
}
}
void* task4(void* arg){
while(1){
pthread_mutex_lock(&me);
printf("5\n");
sleep(1);
pthread_mutex_unlock(&ma);
}
}
int main(int argc, const char *argv[])
{
pthread_mutex_init(&ma,0); //初始化五个互斥锁,并把不需要的互斥锁上锁
pthread_mutex_init(&mb,0);
pthread_mutex_lock(&mb);
pthread_mutex_init(&mc,0);
pthread_mutex_lock(&mc);
pthread_mutex_init(&md,0);
pthread_mutex_lock(&md);
pthread_mutex_init(&me,0);
pthread_mutex_lock(&me);
pthread_t id1,id2,id3,id4; //创建四个线程
pthread_create(&id1,0,task1,0);
pthread_detach(id1);
pthread_create(&id2,0,task2,0);
pthread_detach(id2);
pthread_create(&id3,0,task3,0);
pthread_detach(id3);
pthread_create(&id4,0,task4,0);
pthread_detach(id4);
while(1){
pthread_mutex_lock(&ma);
printf("1\n");
sleep(1);
pthread_mutex_unlock(&mb);
}
return 0;
}