[Linux]线程与同步机制

一、单线程

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.csdn.net/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)

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值