Linux多线程及同步

Linux下实现多线程都是用pthread。UNIX以及类UNIX系统中,线程是以轻量级进程的形式实现。在linux内核中,每个线程也拥有独立的task_struct结构,因此,每个线程也拥有自己独立的pid。一个进程中可以包含多个同时运行的线程,这些线程共享了同一个虚拟内存地址空间和系统资源。

 1 创建进程时,直接使用系统调用:clone(),fork()也是调用clone()。

 2 创建POSIX线程:pthread_create(),实际也是调用的clone()。
 

一. 最简单的多线程

创建5个线程:

#include <iostream>
#include <pthread.h> //多线程相关操作头文件
 
using namespace std;
 
#define NUM_THREADS 5 //线程数
 
//函数返回的是函数指针,便于后面作为参数
void* say_hello(void* args)
{
    cout << "hello..." << endl;
    return 0;
}
 
int main()
{
    pthread_t tids[NUM_THREADS]; //线程id
    for( int i = 0; i < NUM_THREADS; ++i )
    {
        //参数:创建的线程id,线程参数,线程运行函数的起始地址,运行函数的参数
        int ret = pthread_create(&tids[i], NULL, say_hello, NULL);
        if( ret != 0 )
        {
            cout << "pthread_create error:error_code=" << ret << endl;
        }
    }
    //等待各个线程退出后,进程才结束,否则进程强制结束,线程处于未终止的状态
    pthread_exit(NULL);
}

运行结果:

hello...

hello...hello...

hello...

hello...

打印出乱序的原因是因为多线程在竞争CPU资源,争夺运行权。
 

二. 类中的线程

线程调用到函数在一个类中,那必须将该函数声明为静态函数函数。因为静态成员函数属于静态全局区,线程可以共享这个区域,故可以各自调用。

类:

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;
 
class Thread{
        private:
                static void* thread(void*);//静态函数
        public:
                int simpleThread();
};
 
int Thread::simpleThread()
{
    pthread_t id;
    int ret = pthread_create(&id, NULL,thread, NULL);
    if(ret) {
        cerr << "Create pthread error!" << endl;
        return 1;
    }
    pthread_join(id, NULL);
    return 0;
}
 
void* Thread::thread(void* ptr)
{
    for(int i = 0;i < 3;i++)
    {
        sleep(1);
        cout << "This is a pthread." << endl;
    }
    return 0;
}

主函数

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include "Thread.h"
using namespace std;
 
int main()
{
    Thread* newTh = new Thread();
    newTh->simpleThread();
    return 0;
}

运行结果:

This is a pthread.

This is a pthread.

This is a pthread.

三. 带参数的线程调用

#include <iostream>
#include <pthread.h>
 
using namespace std;
 
#define NUM_THREADS 5 //线程数
 
void* say_hello(void* args)
{
        int i = *((int*)args); //对传入的参数进行强制类型转换,由无类型指针转变为整形指针,再用*读取其指向到内容
    cout << "hello " <<i<< endl;
    return 0;
}
 
int main()
{
    pthread_t tids[NUM_THREADS]; //线程id
    for( int i = 0; i < NUM_THREADS; ++i )
    {
        //参数:创建的线程id,线程参数,线程运行函数的起始地址,运行函数的参数
        //传入到函数的参数必须强转为void*类型,即无类型指针,&i表示取i的地址,即指向i的指针
        int ret = pthread_create(&tids[i], NULL, say_hello, (void*)&i);
        pthread_join(tids[i], NULL); //pthread_join用来等待一个线程的结束,是一个线程阻塞的函数
        if( ret != 0 )
        {
            cout << "pthread_create error:error_code=" << ret << endl;
        }
    }
    //等待各个线程退出后,进程才结束,否则进程强制结束,线程处于未终止的状态
    pthread_exit(NULL);
}

运行结果:

hello 0

hello 1

hello 2

hello 3

hello 4

这次运行的结果是按顺序的,因为每创建一个线程后都调用了pthread_join函数。
 

四.线程同步--互斥锁

互斥锁是实现线程同步的一种机制,只要在临界区前后对资源加锁就能阻塞其他进程的访问。
加锁后,锁内的代码只能由该线程独占,其他线程无法调用。

#include <iostream>
#include <pthread.h>
 
using namespace std;
 
#define NUM_THREADS 2 //线程数
 
int sum = 0; //定义全局变量,让所有线程同时写,这样就需要锁机制
pthread_mutex_t sum_mutex; //互斥锁
 
 
void* say_hello(void* args)
{
    int i = *((int*)args);   
    pthread_mutex_lock(&sum_mutex ); //先加锁,再修改sum的值,锁被占用就阻塞,直到拿到锁再修改sum;
    cout << "thread "<<i<<":before sum =" << sum <<endl;
    sum += i;
    cout << "thread "<<i<<":after sum = " << sum <<endl;
    pthread_mutex_unlock( &sum_mutex ); //释放锁,供其他线程使用
    return 0;
}
 
int main()
{
    pthread_t tids[NUM_THREADS]; //线程id
    pthread_mutex_init(&sum_mutex,NULL);
    for( int i = 0; i < NUM_THREADS; i++ )
    {
        int ret = pthread_create(&tids[i], NULL, say_hello, (void*)&i);     
    }
    for( int i = 0; i < NUM_THREADS; i++ )
    {
        pthread_join(tids[i], NULL);     
    }
    cout<<"finally,sum="<<sum<<endl;
    //等待各个线程退出后,进程才结束,否则进程强制结束,线程处于未终止的状态
    pthread_exit(NULL);   
}

运行结果一:

thread 1:before sum =0
thread 1:after sum = 1
thread 2:before sum =1
thread 2:after sum = 3
finally,sum=3

运行结果二:

thread 2:before sum =0
thread 2:after sum = 2
thread 2:before sum =2
thread 2:after sum = 4
finally,sum=4

为什么sum值还是不确定呢?i怎么会是1和2???
因为传进参数时,传递的是i=0的地址,i值在不断改变。用一个数组来存i值:

int index[NUM_THREAD];//用来保存i的值避免被修改
pthread_create(&tids[i], NULL, say_hello, (void*)&(index[i]));

运行结果(改成3个线程):
thread 0:before sum =0
thread 0:after sum = 0
thread 1:before sum =0
thread 1:after sum = 1
thread 2:before sum =1
thread 2:after sum = 3
finally,sum=3
这样就正确了,按顺序打印出来了,互斥锁的作用起到了。
 

五. 线程同步--条件变量

条件变量是线程同步的另一种实现机制,操作有signal和wait。条件变量总是和互斥量结合使用,条件变量就共享变量的状态改变发出通知,这里的信号,与linux中signal无关,意思为发出信号。

signal(发送信号):通知一个或多个处于等待状态的线程,某个共享变量的状态已经改变。

wait(等待):收到一个通知前一直处于阻塞状态。

与cond相关的判断代码设计时,必须是一个while循环来控制对wait函数的调用。

#include <iostream>
#include <pthread.h>
 
using namespace std;  
 
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;/*初始化互斥锁*/
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;/*初始化条件变量*/ 
/*thread1打印10以内的偶数,thread2打印奇数*/
 
int i= 0;
 
void *thread1(void *args)  
{  
    for(i=0;i<=9;i++)//i是全局变量
    {
        pthread_mutex_lock(&mutex);
        if(i%2==0){
            cout << "thread 1,i=" << i<<endl;       
        }else{
            pthread_cond_signal(&cond);/*i变成奇数,条件改变,发送信号,通知其他线程*/
            cout<<"thread 1,i="<<i<<",signal other threads"<<endl;
        }
        pthread_mutex_unlock(&mutex);
    }  
    
    return 0;
}
 
 
void *thread2(void *args)
{   
    while(1)
    {
        pthread_mutex_lock(&mutex);
        if(i%2==0){//i是偶数线程就挂起
            /*操作有2步:
            第一解锁,先解除之前的pthread_mutex_lock锁定的mutex;
            第二挂起,阻塞并在等待队列里休眠,即所在线程挂起,直到再次被再次唤醒*/
            pthread_cond_wait(&cond,&mutex);/*wait必须和互斥锁同时用在一个线程里,它同时起到对资源的加锁和解锁*/
            cout<<"pthread_cond_wait"<<endl;       
        }     
        cout << "thread 2,i=" << i<<endl;
        pthread_mutex_unlock(&mutex);
    }
    return 0;
}  
 
 
int main()  
{  
    pthread_t t_a;  
    pthread_t t_b;  
    pthread_create(&t_a,NULL,thread1,NULL);/*再创建进程t_a*/
    pthread_create(&t_b,NULL,thread2,NULL); /*先创建进程t_b*/   
    pthread_join(t_a, NULL);/*等待进程t_a结束*/  
    pthread_join(t_b, NULL);/*等待进程t_b结束*/  
    pthread_mutex_destroy(&mutex);  
    pthread_cond_destroy(&cond);  
    return 0;
}

运行结果(有多种运行结果):

thread 1,i=0
thread 1,i=1,signal other threads
thread 1,i=2
thread 1,i=3,signal other threads
thread 1,i=4
thread 1,i=5,signal other threads
pthread_cond_wait
thread 2,i=6
thread 1,i=6
thread 1,i=7,signal other threads
thread 1,i=8
thread 1,i=9,signal other threads
pthread_cond_wait
thread 2,i=10

多文件编译,Makefile文件如下:

INC =  -I./include
SRC = ./src
FLAGE = -pthread
CFLAG = -g -O3 -Wall -Wno-deprecated #-DSHOW_DEBUG #-pipe  -D_NEW_LIC -D_GNU_SOURCE -D_REENTRANT  -z defs
CC = g++
EXE_DIR = ./bin
EXE = $(EXE_DIR)/main.o \
          $(EXE_DIR)/hello.o \
          $(EXE_DIR)/para.o \
          $(EXE_DIR)/mutex.o \
          $(EXE_DIR)/signal.o \
        
all: $(EXE)
 
$(EXE_DIR)/%.o:$(SRC)/%.cpp
        $(CC) $(CFLAG) $(FLAGE) $(INC) $< -o $@
        
clean: 
        rm -rf $(EXE_DIR)/*.o
        rm -rf $(EXE)

总结:线程提供的同步机制是有代价,需要操作系统来调度,切换,加锁等,都是消耗资源的。

互斥量提供了对共享变量的独占式访问,条件变量允许一个或多个线程等待通知,其他线程改变了共享变量的状态。

线程的同步机制复杂,易出错,且开销比较大,所以衍生出了协程(微线程)的概念。

猜你喜欢

转载自blog.csdn.net/qq_34793133/article/details/82177847