linux线程同步

0 概述

  所谓同步,就是给多个线程规定一个执行的顺序(或称为时序),要求某个线程先执行完一段代码后,另一个线程才能开始执行。

  

第一种情况:多个线程访问同一个变量

  1. 一个线程写,其它线程读:这种情况不存在同步问题,因为只有一个线程在改变内存中的变量,内存中的变量在任意时刻都有一个确定的值;
  2. 一个线程读,其它线程写:这种情况会存在同步问题,主要是多个线程在同时写入一个变量的时候,可能会发生一些难以察觉的错误,导致某些线程实际上并没有真正的写入变量;
  3. 几个线程写,其它线程读:情况同2。

  多个线程同时向一个变量赋值,就会出现问题,这是为什么呢?

  我们编程采用的是高级语言,这种语言是不能被计算机直接执行的,一条高级语言代码往往要编译为若干条机器代码,而一条机器代码,CPU也不一定是在一个CPU周期内就能完成的。计算机代码必须要按照一个“时序”,逐条执行。

  举个例子,在内存中有一个整型变量number(4字节),那么计算++number(运算后赋值)就至少要分为如下几个步骤:

  1. 寻址:由CPU的控制器找寻到number变量所在的地址;
  2. 读取:将number变量所在的值从内存中读取到CPU寄存器中;
  3. 运算:由CPU的算术逻辑运算器(ALU)对number值进行计算,将结果存储在寄存器中;
  4. 保存:由CPU的控制器将寄存器中保存的结果重新存入number在内存中的地址。

  这是最简单的时序,如果牵扯到CPU的高速缓存(CACHE),则情况就更为复杂了。

CPU结构简图图1 CPU结构简图

  在多线程环境下,当几个线程同时对number进行赋值操作时(假设number初始值为0),就有可能发生冲突: 

  当某个线程对number进行++操作并执行到步骤2(读取)时(0保存在CPU寄存器中),发生线程切换,该线程的所有寄存器状态被保存到内存后后,由另一个线程对number进行赋值操作。当另一个线程对number赋值完毕(假设将number赋值为10),切换回第一个线程,进行现场恢复,则在寄存器中保存的number值依然为0,该线程从步骤3继续执行指令,最终将1写入到number所在内存地址,number值最终为1,另一个线程对number赋值为10的操作表现为无效操作。

摘自: C#基本线程同步         http://blog.csdn.net/mousebaby808/article/details/5477733

 

因此我们在访问资源时应该使用线程同步技术,以避免不必要的麻烦

 

 

在使用多线程技术的时候尽量使用线程安全函数,为了使用线程安全函数,应该在声明头文件前定义宏_REENTRANT   或者在编译时加上参数  -D_REENTRANT

 

linux线程同步

1、互斥锁(mutex)

2、信号量(sem)

3、条件变量(cond)

 

这里只介绍两种方法:互斥锁与信号量

互斥锁的使用

  1. 初始化锁。
    静态分配:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    动态分配:int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_attr_t *mutexattr);
  2. 加锁。对共享资源的访问,要对互斥量进行加锁,如果互斥量已经上了锁,调用线程会阻塞,直到互斥量被解锁。
    int pthread_mutex_lock(pthread_mutex *mutex);
    int pthread_mutex_trylock(pthread_mutex_t *mutex);
  3. 解锁。在完成了对共享资源的访问后,要对互斥量进行解锁。否则会造成死锁
    int pthread_mutex_unlock(pthread_mutex_t *mutex);
  4. 销毁锁。锁在是使用完成后,需要进行销毁以释放资源。
    int pthread_mutex_destroy(pthread_mutex *mutex);

返回值:成功返回0, 失败返回其他值

参数mutex             指向互斥锁变量的指针

参数mutexattr        要初始化的锁属性

注:

1、由于静态初始化方式是由宏完成的,而宏又是预编译器处理的,所以不建议使用静态的方式去初始化锁,不需要配置互斥锁属性的时候第二个参数传递NULL

2、应该在每个访问资源的代码处加上如下形式的代码:

         pthread_mutex_lock(&mutex)

         //访问资源…..

         intpthread_mutex_unlock(&mutex);

 

示例代码:

 

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#define NUM_THREAD 100
void *thread_inc(void *arg);
void *thread_des(void *arg);

long long num = 0;
pthread_mutex_t mutex;

int main(int argc, char *argv[])
{
    pthread_t thread_id[NUM_THREAD];
    int i = 0;

    pthread_mutex_init(&mutex, NULL);	//初始化锁

    for(i = 0; i < NUM_THREAD; i++)
    {
        if( i % 2 )
            pthread_create(&(thread_id[i]), NULL, thread_inc, NULL);
        else
            pthread_create(&(thread_id[i]), NULL, thread_des, NULL);
    }

    for (i = 0; i < NUM_THREAD; i++)
        pthread_join(thread_id[i], NULL);

    printf("result: %lld \n", num);
    pthread_mutex_destroy(&mutex);   //销毁锁
    return 0;
}
//下面的thread_inc与thread_des中加锁与解锁的位置各有不同,按需选择

//将加锁与解锁的动作放在循环外,最大限度的减少了pthread_mutex_lock()与pthread_mutex_unlock()的调用次数,避免了多次调用这两个函数,提高了线程运行的速度(加锁与解锁需要耗费相一定的时间),但是这也导致其他线程必须阻塞直到当前线程完成循环才能访问变量num
void *thread_inc(void *arg)
{
	int i = 0;
    pthread_mutex_lock(&mutex);  //加锁
    for (int i = 0; i < 1000000; i++)
        num += 1;
    pthread_mutex_unlock(&mutex); //解锁
    return NULL;
}

//将加锁与解锁放在循环内,调用pthread_mutex_lock()与pthread_mutex_unlock()消耗的时间虽然很多,但是每次解锁之后其他线程有可能得到访问变量num的机会
void *thread_des(void *arg)
{
	int i = 0;
    for (int i = 0; i < 1000000; i++)
    {
        pthread_mutex_lock(&mutex);	//加锁
        num -= 1;
        pthread_mutex_unlock(&mutex);	//解锁
    }
    return NULL;
}


 

信号量的使用

  1. 信号量初始化。
    intsem_init (sem_t *sem , int pshared, unsigned int value);
  2. 等待信号量。如果信号量的值大于0,信号量的值将减1,立即返回。如果信号量的值为0,则线程阻塞。
    int sem_wait(sem_t *sem);
  3. 释放信号量。信号量值将加1。并通知其他阻塞线程。
    int sem_post(sem_t *sem);
  4. 销毁信号量。我们用完信号量后都它进行清理。归还占有的一切资源。
    int sem_destroy(sem_t *sem);

返回值: 成功返回0, 失败返回其他值

参数sem 指向信号量的指针

参数pshared   传递0时,信号量只能在当前进程内使用,其他值时可多个进程共享

参数value       指定信号量的初始值

 

注:

应该在每个访问资源的代码处加上如下形式的代码:

//假设信号量初始值为1

sem_wait(&sem); //执行此句代码后信号量为0

//访问资源

sem_post(&sem);// 执行此句代码后信号量为1

信号量的值在0与1之间跳转,这种信号量为二进制信号量

示例代码:

 

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>

void *read(void *arg);
void *accu(void *arg);
static sem_t sem_one;	//注意这里使用两个信号量使两个子线程交替访问变量num
static sem_t sem_two;
static int num = 0;


int main(int argc, char *argv[])
{
    pthread_t id_t1, id_t2;
    sem_init(&sem_one, 0, 0);	//初始信号量sem_two初值为0
    sem_init(&sem_two, 0, 1);	//初始信号量sem_two初值为1

    pthread_create(&id_t1, NULL, read, NULL);
    pthread_create(&id_t2, NULL, accu, NULL);

    pthread_join(id_t1, NULL);
    pthread_join(id_t2, NULL);

    sem_destroy(&sem_one);	//销毁信号量
    sem_destroy(&sem_two);

    return 0;
}


void *read(void *arg)
{
    int i = 0;
    for (i = 0; i < 5; i++) 
	{
        fputs("Input num: ", stdout);
        sem_wait(&sem_two);
        scanf("%d", &num);
        sem_post(&sem_one);
    }
    return NULL;
}

void *accu(void *arg)
{
    int sum = 0 , i = 0;
    for (i = 0; i < 5; i++) 
	{
        sem_wait(&sem_one);
        sum+= num;
        sem_post(&sem_two);
    }
    printf("Result: %d \n", sum);
    return NULL;
}

 

简单聊天室示例代码

服务器端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h>

#define BUF_SIZE 100
#define MAX_CLNT 256

void *handle_clnt(void *arg);
void send_msg(char *msg, int len);
void error_handling(const char *msg);
int clnt_cnt = 0;
int clnt_socks[MAX_CLNT] = { 0 };
pthread_mutex_t mutx;

int main(int argc, char *argv[])
{
    int serv_sock = -1, clnt_sock = -1;
    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t clnt_adr_sz;
    pthread_t t_id;
	
    if( 2 != argc ) 
	{
        printf("Usage : %s <port> \n", argv[0]);
        exit(-1);
    }

    pthread_mutex_init(&mutx, NULL);	//初始化互斥锁
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    if( -1 == bind(serv_sock, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) )
        error_handling("bind() error");
    if( -1 == listen(serv_sock, 5) )
        error_handling("listen() error");

    while (1)
    {
        clnt_adr_sz = sizeof(clnt_adr);
        clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz); //阻塞直到新连接到来

        pthread_mutex_lock(&mutx); //加锁
        clnt_socks[clnt_cnt++] = clnt_sock; //将新建立连接的客户端套接字保存到clnt_socks数组里
        pthread_mutex_unlock(&mutx); //解锁

        pthread_create(&t_id, NULL, handle_clnt, (void*)&clnt_sock);	//创建子线程
        pthread_detach(t_id); //分离子线程,不关心子线程返回值

        printf("Connected client IP: %s \n", inet_ntoa(clnt_adr.sin_addr));
    }

    close(serv_sock);	//关闭服务器端套接字
	pthread_mutex_destroy(&mutx);   //销毁锁
    return 0;
}


void *handle_clnt(void *arg)
{
    int clnt_sock = *((int *)arg);
    int str_len = 0, i = 0;
    char msg[BUF_SIZE] = { 0 };

    while ((str_len = read(clnt_sock, msg, sizeof(msg))) != 0)
        send_msg(msg, str_len);

	//能执行到这里说明读到EOF
    pthread_mutex_lock(&mutx);
    for (i = 0; i < clnt_cnt; i++)
    {
        if( clnt_sock == clnt_socks[i] )	//找到EOF客户端套接字
        {
            while (i < clnt_cnt - 1)	
			{
				clnt_socks[i] = clnt_socks[i + 1];	//循环将clnt_socks数组的第i个到倒数第二个都用后一个替换
				i++;
			}                
            break;
        }
    }
    clnt_cnt--;	
    pthread_mutex_unlock(&mutx);
    close(clnt_sock);	//释放客户端套接字
    return NULL;
}

//向所有连接的客服端发送消息
void send_msg(char *msg, int len)
{
    int i = 0;
    pthread_mutex_lock(&mutx);
    for (i = 0; i < clnt_cnt; i++)
        write(clnt_socks[i], msg, len);
    pthread_mutex_unlock(&mutx);
}

void error_handling(const char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(-1);
}

 

 

客户端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h>

#define BUF_SIZE 100
#define NAME_SIZE 20

void *send_msg(void *arg);
void *recv_msg(void *arg);
void error_handling(const char *message);

char name[NAME_SIZE] = "[DEFAULT]";
char msg[BUF_SIZE] = { 0 };

int main(int argc, const char *argv[]) 
{
    int sock = -1;
    struct sockaddr_in serv_addr;
    pthread_t snd_thread, rcv_thread;
    void *thread_return = NULL;

    if( 4 != argc )
    {
        printf("Usage: %s <IP> <port> \n", argv[0]);
        exit(-1);
    }

    sprintf(name, "[%s]", argv[3]); 
    sock = socket(PF_INET, SOCK_STREAM, 0);
    if( -1 == sock )
        error_handling("socket() error");

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_addr.sin_port = htons(atoi(argv[2]));

    if( -1 == connect(sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) )
        error_handling("connect() error");

    //通过多线程分离I/O
    pthread_create(&snd_thread, NULL, send_msg, (void *)&sock);
    pthread_create(&rcv_thread, NULL, recv_msg, (void *)&sock);
    //阻塞直到线程死亡,获取其返回值
    pthread_join(snd_thread, &thread_return);
    pthread_join(rcv_thread, &thread_return);

    close(sock);		//关闭套接字
    return 0;
}

//发送消息
void *send_msg(void *arg)
{
    int sock = *((int *)arg);
    char name_msg[NAME_SIZE + BUF_SIZE] = { 0 };
    while (1) 
	{
        fgets(msg, BUF_SIZE, stdin);
        if( !strcmp(msg, "q\n") || !strcmp(msg, "Q \n") ) //输入q退出进程
		{
            close(sock);
            exit(0);				//进程死亡时,一切资源都将被释放,在此处结束进程可行
        }
        sprintf(name_msg, "%s  %s", name, msg);
        write(sock, name_msg, strlen(name_msg));
    }
    return NULL;
}

//接收消息
void *recv_msg(void *arg)
{
    int sock = *((int *)arg);
    char name_msg[NAME_SIZE + BUF_SIZE] = { 0 };
    int str_len = -1;
    while (1) 
	{
        str_len = read(sock, name_msg, NAME_SIZE + BUF_SIZE - 1);
        if( -1 == str_len )
            return (void *)-1;	//pthread_exit((void *)-1);
        name_msg[str_len] = 0;	//添加字符串末尾的空字符
        fputs(name_msg, stdout);
    }
    return NULL;
}

void error_handling(const char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(-1);
}


 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值