一、单线程
1.1 源码
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <errno.h>
void sleep_us(unsigned long uSec){
struct timeval tv;
tv.tv_sec=uSec/1000000;
tv.tv_usec=uSec%1000000;
int err;
do{
err=select(0,NULL,NULL,NULL,&tv);
}while(err<0 && errno==EINTR);
}
void* route(void *arg)
{
char* thread_name = (char*)arg;
while(1){
if(ticket > 0){
printf("thread:%s sells ticket:%d\n",thread_name,ticket);
ticket--;
sleep_us(1000000);
}else{
break;
}
}
}
void test_single_thread(){
pthread_t t1;
pthread_create(&t1,NULL,route,(void*)"thread_1");
pthread_join(t1,NULL);
}
int main(){
test_single_thread();
return 0;
}
1.2 结果
thread:thread_1 sells ticket:10
thread:thread_1 sells ticket:9
thread:thread_1 sells ticket:8
thread:thread_1 sells ticket:7
thread:thread_1 sells ticket:6
thread:thread_1 sells ticket:5
thread:thread_1 sells ticket:4
thread:thread_1 sells ticket:3
thread:thread_1 sells ticket:2
thread:thread_1 sells ticket:1
二、多线程
2.1 源码
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <errno.h>
int ticket = 10;
void sleep_us(unsigned long uSec){
struct timeval tv;
tv.tv_sec=uSec/1000000;
tv.tv_usec=uSec%1000000;
int err;
do{
err=select(0,NULL,NULL,NULL,&tv);
}while(err<0 && errno==EINTR);
}
void* route(void *arg)
{
char* thread_name = (char*)arg;
while(1){
if(ticket > 0){
printf("thread:%s sells ticket:%d\n",thread_name,ticket);
ticket--;
sleep_us(1000000);
}else{
break;
}
}
}
void test_multi_thread(){
pthread_t t1;
pthread_t t2;
pthread_create(&t1,NULL,route,(void*)"thread_1");
pthread_create(&t2,NULL,route,(void*)"thread_2");
pthread_join(t1,NULL);
pthread_join(t2,NULL);
}
int main(){
test_multi_thread();
return 0;
}
2.2 结果
thread:thread_2 sells ticket:10
thread:thread_1 sells ticket:10
thread:thread_1 sells ticket:8
thread:thread_2 sells ticket:8
thread:thread_2 sells ticket:6
thread:thread_1 sells ticket:6
thread:thread_1 sells ticket:4
thread:thread_2 sells ticket:4
thread:thread_2 sells ticket:2
thread:thread_1 sells ticket:2
thread:thread_2 sells ticket:0
错误结果:线程1和线程2同时卖同一票
三、多线程与同步机制
3.1 互斥锁(mutex)
3.1.1 函数
- 初始化锁
静态分配:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
动态分配:int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_attr_t *mutexattr)
- 加锁
int pthread_mutex_lock(pthread_mutex *mutex)
int pthread_mutex_trylock(pthread_mutex_t *mutex)
- 解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex)
- 销毁锁
int pthread_mutex_destroy(pthread_mutex *mutex)
3.1.2 源码
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <errno.h>
pthread_mutex_t mutex; //定义
int ticket = 10;
void sleep_us(unsigned long uSec){
struct timeval tv;
tv.tv_sec=uSec/1000000;
tv.tv_usec=uSec%1000000;
int err;
do{
err=select(0,NULL,NULL,NULL,&tv);
}while(err<0 && errno==EINTR);
}
void* route_mutex(void *arg)
{
char* thread_name = (char*)arg;
while(1){
pthread_mutex_lock(&mutex);
if(ticket > 0){
printf("thread:%s sells ticket:%d\n",thread_name,ticket);
ticket--;
pthread_mutex_unlock(&mutex);
sleep_us(1000000);//注意休眠的位置
}else{
pthread_mutex_unlock(&mutex);
break;
}
}
}
void test_multi_thread_mutex(){
pthread_t t1;
pthread_t t2;
pthread_mutex_init(&mutex,NULL); //初始化锁
pthread_create(&t1,NULL,route_mutex,(void*)"thread_1");
pthread_create(&t2,NULL,route_mutex,(void*)"thread_2");
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_mutex_destroy(&mutex);//销毁锁
}
int main(){
test_multi_thread_mutex();
return 0;
}
3.1.3 结果
thread:thread_1 sells ticket:10
thread:thread_2 sells ticket:9
thread:thread_2 sells ticket:8
thread:thread_1 sells ticket:7
thread:thread_2 sells ticket:6
thread:thread_1 sells ticket:5
thread:thread_2 sells ticket:4
thread:thread_1 sells ticket:3
thread:thread_1 sells ticket:2
thread:thread_2 sells ticket:1
正确结果:某一张票只能由线程1或者线程2卖出
3.2 条件变量(cond)
3.2.1 函数
- 初始化条件变量
静态态初始化,pthread_cond_t cond = PTHREAD_COND_INITIALIER
动态初始化,int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr)
- 等待条件成立(释放锁,同时阻塞等待条件变量为真才行)
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime)
- 唤醒条件变量
int pthread_cond_signal(pthread_cond_t *cond)
int pthread_cond_broadcast(pthread_cond_t *cond); //解除所有线程的阻塞
- 清除条件变量
int pthread_cond_destroy(pthread_cond_t *cond)
3.2.2 源码
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <errno.h>
pthread_mutex_t mutex;
pthread_cond_t cond; //定义
int ticket = 10;
void sleep_us(unsigned long uSec){
struct timeval tv;
tv.tv_sec=uSec/1000000;
tv.tv_usec=uSec%1000000;
int err;
do{
err=select(0,NULL,NULL,NULL,&tv);
}while(err<0 && errno==EINTR);
}
void* route_consume_cond(void *arg)
{
char* thread_name = (char*)arg;
while(1){
pthread_mutex_lock(&mutex); //上锁
if(ticket <= 0){
printf("thread:%s has ticket:%d,waiting...\n",thread_name,ticket);
pthread_cond_wait(&cond,&mutex);//条件不成立时挂起等待
}
if(ticket > 0){
printf("thread:%s sells ticket:%d\n",thread_name,ticket);
ticket--; //修改
}
pthread_mutex_unlock(&mutex); //解锁
sleep_us(1000000);
}
}
void* route_produce_cond(void *arg)
{
char* thread_name = (char*)arg;
while(1){
pthread_mutex_lock(&mutex); //上锁
printf("thread:%s sells ticket:%d\n",thread_name,ticket);
ticket++;
pthread_cond_signal(&cond); //唤醒
pthread_mutex_unlock(&mutex); //解锁
sleep_us(1200000);
}
}
void test_multi_thread_available(){
pthread_t t1;
pthread_t t2;
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond,NULL);
pthread_create(&t1,NULL,route_consume_cond,(void*)"consume");
pthread_create(&t2,NULL,route_produce_cond,(void*)"produce");
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
}
int main(){
test_multi_thread_available();
return 0;
}
3.2.3 结果
thread:consume sells ticket:10
thread:produce sells ticket:10
thread:consume sells ticket:10
thread:produce sells ticket:9
thread:consume sells ticket:10
thread:produce sells ticket:9
thread:consume sells ticket:10
...
thread:consume sells ticket:1
thread:produce sells ticket:0
thread:consume sells ticket:1
thread:produce sells ticket:0
thread:consume sells ticket:1
thread:produce sells ticket:0
thread:consume sells ticket:1
thread:produce sells ticket:0
thread:consume sells ticket:1
正常结果:(案例是:窗口票卖出快,窗口进票慢)最后出现,只有等到produce线程进了1正票之后,consume线程才能再卖出1张
3.2.4 pthread_cond_wait 参数二
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
cond: 要在这个条件变量上等待(把当前线程挂起)
mutex: 互斥量(把当前线程拿到的锁释放)
为什么第二个参数要传互斥量?
因为等待条件满足是在临界区里等待的,如果某个线程直接进行等待,那么就会抱着锁去等待,此时,其他线程也无法进入临界资源。
剖析:pthread_cond_wait临时将当前线程拿到的mutex释放,允许其他线程占有mutex,不必等待.
这里route_consume_cond通过pthread_mutex_lock先占有mutex,之后通过pthread_cond_wait等待条件变量但会先临时释放mutex,使得其他线程可临时能占有锁;
这样,route_produce_cond线程到达pthread_mutex_lock时,是能占有mutex,不必等待.之后会pthread_cond_signal唤醒条件变量.
3.2.5 条件变量使用规范
(1)等待条件
pthread_mutex_lock(&lock); #上锁
while(条件不成立)
{
pthread_cond_wait(&cond); #条件不成立时挂起等待(注意是while)
}
进行操作...... #操作
pthread_mutex_unlock(&lock); #解锁
(2)给条件发信号
pthread_mutex_lock(&lock); #上锁
设置条件为真... #符合条件
pthread_cond_signal(&cond); #唤醒
pthread_mutex_unlock(&lock); #解锁
3.3 信号量(sem)
3.3.1 函数
- 信号量初始化
int sem_init (sem_t *sem , int pshared, unsigned int value)
#sem:指定的信号量进行初始化,
#pshared:表示此信号量是在进程间共享还是线程间共享,设置好它的共享选项(linux 只支持为0,即表示它是当前进程的局部 信号量),
#value:信号量的初始值
- 等待信号量
int sem_wait(sem_t *sem)
#如果信号量的值大于0,将信号量的值减1,立即返回。如果信号量的值为0,则线程阻塞
- 释放信号量。信号量值加1。并通知其他等待线程
int sem_post(sem_t *sem)
- 销毁信号量
int sem_destroy(sem_t *sem)
3.3.2 源码
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <errno.h>
#include <semaphore.h>
sem_t sem[2];
int ticket = 10;
void* route_consume_sem(void *arg)
{
char* customer_name = (char*)arg;
while(1){
sem_wait(&sem[0]);//add lock for sem[0]
ticket--;
printf("thread:%s consume ticket:%d\n",customer_name,ticket);
sem_post(&sem[1]);//release lock for sem[0]
sleep_us(1000000);
}
}
void* route_produce_sem(void *arg)
{
char* customer_name = (char*)arg;
while(1){
sem_wait(&sem[1]); //add lock for sem[1]
ticket++;
printf("thread:%s produce ticket:%d\n",customer_name,ticket);
sem_post(&sem[0]); //release lock for sem[0]
sleep_us(1000000);
}
}
void test_multi_thread_sem(){
pthread_t t1;
pthread_t t2;
sem_init(&sem[0],0,0); //For consume thread, init value is 0
sem_init(&sem[1], 0, 1);//For produce thread, init value is 1 //
pthread_create(&t1,NULL,route_consume_sem,(void*)"consume");
pthread_create(&t2,NULL,route_produce_sem,(void*)"produce");
pthread_join(t1,NULL);
pthread_join(t2,NULL);
sem_destroy(&sem[0]);
sem_destroy(&sem[1]);
}
int main(){
test_multi_thread_sem();
return 0;
}
3.3.3 结果
thread:produce produce ticket:11
thread:consume consume ticket:10
thread:produce produce ticket:11
thread:consume consume ticket:10
thread:produce produce ticket:11
thread:consume consume ticket:10
thread:produce produce ticket:11
thread:consume consume ticket:10
3.4 区别
[参考:https://blog.youkuaiyun.com/a987073381/article/details/52029070]
互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
同步:主要是流程上的概念,是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的
(1)互斥锁:互斥,一个线程占用了某个资源,那么其它的线程就无法访问,直到这个线程解锁,其它线程才可以访问
(2)条件变量:同步,一个线程完成了某一个动作就通过条件变量发送信号告诉别的线程,别的线程再进行某些动作
(3)信号量:同步,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动
附Makefile:
##dir
PWD_DIR=$(shell pwd)
OBJ_DIR=$(PWD_DIR)/obj
BIN_DIR=$(PWD_DIR)/bin
##compiler
CC=gcc
##target
TARGET=e_pthread
##build
$(TARGET):main.o
$(CC) main.o -o $@ -lpthread
main.o:main.cpp
$(CC) -c $^
clean:
rm -rf *.o $(TARGET)