This question comes mainly from curiosity. I’m not quite sure how to phrase it best. Especially in a title. But I’m wondering if say you have one thread writing to a variable of an essentially primitive type and one thread reading them at the same time if there’s any likelihood of the read happening while the variable is half written causing either weird values or undefined behavior.

Take something like a value of 8 bits from 00010101 to 11101000.

I’m imagining if say 4 bits are written while we try to read it the result could be something like

11100101

To play around i made this small sample rust. It passed without making garbage. Printing at first a bunch of lines stating “String = Hello!” and second “String = Hi!” without weirdness or issues. I kind of half-expected something like “String = #æé¼¨A” or a segfault.

use std::thread::{self, JoinHandle, sleep};

const HELLO: &str = "Hello!";
const HI: &str = "Hi!";

struct ExemptSyncStringSlice<'a>(&'a str);

unsafe impl Sync for ExemptSyncStringSlice<'_> {}

fn print_ptr(pointer: *const ExemptSyncStringSlice)
{
	for _ in 1..500
	{
		unsafe
		{
			println!("String = {}", (*pointer).0);
		}
	}
}

fn main()
{
	
	static mut DESYNC_POINTER: ExemptSyncStringSlice = ExemptSyncStringSlice(HELLO);

	let join_handle: JoinHandle<()> = thread::spawn
	(
		|| {
			print_ptr(&raw const DESYNC_POINTER);
		}
	);
	sleep(time::Duration::from_millis(1));
	unsafe { DESYNC_POINTER.0 = HI; }
	
	join_handle.join().unwrap();
}
  • Killercat103@slrpnk.netOP
    link
    fedilink
    arrow-up
    1
    ·
    3 months ago

    For anyone interested, I did make a second version of the same logic. Even if i kind of had atomic operations in mind i did not know that i was thinking of atomic or what atomic was for that matter. So in a broad sense i assumed raw pointers were atomic. Also worth noting implementing a false Sync for &str seemed to be unnecessary to get it to run in the first example even if i got the impression during construction that it was. Just pretend in the first example “ExemptSyncStringSlice” is just &str if you want to compare them for some reason.

    I don’t know the overhead of AtomicPtr compared to raw pointers and it is dependent on operating systems that can use an atomic load store (whatever that means.) But this version seems more “correct” at least.

    use std::sync::atomic::{AtomicPtr, Ordering};
    use std::time::Duration;
    
    
    fn main()
    {
    	let pointer: AtomicPtr<&'static str> = AtomicPtr::new(&mut "Hello!");
    
    	let mut value2: &'static str = "Hi!"; // Place outside scope due to lifetime.
    
    	thread::scope(|scope|
    	{
    		scope.spawn
    		(|| {
    			for _ in 1..1000
    			{
    				unsafe { println!("String = {}",  *pointer.load(Ordering::Relaxed)); }
    			}
    		});
    
    		scope.spawn
    		(|| {
    			sleep(Duration::from_millis(1));
    			pointer.store(&mut value2, Ordering::Relaxed)
    		});
    	});
    }
    

    Thank you all who responded btw.